Changes: Remove left-over debug message
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactsaverequest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Mobility Components.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "contactsaverequest.h"
43 #include "engine.h"
44 #include "guidalgorithm.h"
45
46 #include <dao/classhierarchy.h>
47 #include <dao/contactdetail.h>
48 #include <dao/contactdetailschema.h>
49 #include <dao/querybuilder.h>
50 #include <dao/subject.h>
51 #include <dao/support.h>
52
53 #include <lib/customdetails.h>
54 #include <lib/settings.h>
55
56 #include <dbus/tracker.h>
57
58 #include <QtGui/QImageWriter>
59
60 #include <errno.h>
61
62 ///////////////////////////////////////////////////////////////////////////////////////////////////
63
64 using namespace SopranoLive;
65 using namespace SopranoLive::Ontologies;
66
67 ///////////////////////////////////////////////////////////////////////////////////////////////////
68
69 typedef QMap<QString, RDFVariable> AffiliationMap;
70 typedef QList<class ExplictObjectInfo> ExplictObjectInfoList;
71 typedef QMap<QString, ExplictObjectInfoList> ExplictObjectInfoListMap;
72 typedef QList<class DetailMapping> DetailMappingList;
73 typedef QMultiHash<PredicateChain, const QUrl *> EntityHash;
74
75 ///////////////////////////////////////////////////////////////////////////////////////////////////
76
77 class DetailMappingData : public QSharedData
78 {
79     friend class DetailMapping;
80
81 public: // constructors
82     DetailMappingData(const QContactDetail &genericDetail,
83                       const QTrackerContactDetail *trackerDetail)
84         : m_genericDetail(genericDetail)
85         , m_trackerDetail(trackerDetail)
86         , m_contextsNormalized(false)
87     {
88     }
89
90 private: // fields
91     QContactDetail                  m_genericDetail;
92     const QTrackerContactDetail *   m_trackerDetail;
93     bool                            m_contextsNormalized : 1;
94 };
95
96 ///////////////////////////////////////////////////////////////////////////////////////////////////
97
98 class DetailMapping
99 {
100 public: // constructors
101     DetailMapping(const QContactDetail &genericDetail,
102                   const QTrackerContactDetail *trackerDetail)
103         : d(new DetailMappingData(genericDetail, trackerDetail))
104     {
105     }
106
107 public: // attributes
108     /// a detail which has no own ontology representation
109     bool isCustomDetail() const;
110
111     /// is a detail which is generated from other details and not stored itself
112     bool isSyntheticDetail() const;
113
114     QString definitionName() const;
115     QStringList contexts() const;
116
117     QString detailUri() const;
118     QStringList linkedDetailUris() const;
119
120     QVariant fieldValue(const QString &fieldName) const;
121     QVariantMap fieldValues() const;
122
123     const QList<QTrackerContactDetailField> & trackerFields() const;
124     const QTrackerContactDetailField * subTypeField() const;
125     QTrackerContactSubject::Scheme detailUriScheme() const;
126
127 public: // methods
128     QUrl makeResourceIri(QContactLocalId contactId) const;
129     bool updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings);
130
131 private: // fields
132     QExplicitlySharedDataPointer<DetailMappingData> d;
133 };
134
135 ///////////////////////////////////////////////////////////////////////////////////////////////////
136
137 class ExplictObjectInfoData : public QSharedData
138 {
139     friend class ExplictObjectInfo;
140
141 private: // constructor
142     ExplictObjectInfoData(const QUrl &typeIri, const QUrl &objectIri,
143                           const QTrackerContactDetail &detail)
144         : m_typeIri(typeIri.toString())
145         , m_objectIri(objectIri.toString())
146         , m_hasCustomFields(not detail.customValueChains().isEmpty())
147     {
148         QSet<QString> predicateIriSet;
149         QSet<QString> subTypeIriSet;
150
151         foreach(const QTrackerContactDetailField &field, detail.fields()) {
152             if (field.hasSubTypes()) {
153                 foreach(const ClassInfoBase &ci, field.subTypeClasses()) {
154                     subTypeIriSet += ci.iri().toString();
155                 }
156
157                 continue;
158             }
159
160             if (field.isWithoutMapping() || field.isReadOnly() || field.isInverse()) {
161                 continue;
162             }
163
164             predicateIriSet += field.propertyChain().last().iri().toString();
165
166             foreach(const PropertyInfoBase &pi, field.computedProperties()) {
167                 predicateIriSet += pi.iri().toString();
168             }
169         }
170
171         m_predicateIris = predicateIriSet.toList();
172         m_subTypeIris = subTypeIriSet.toList();
173     }
174
175 private: // fields
176     QString m_typeIri;
177     QString m_objectIri;
178     QStringList m_predicateIris;
179     QStringList m_subTypeIris;
180     bool m_hasCustomFields : 1;
181 };
182
183 class ExplictObjectInfo
184 {
185 public: // constructor
186     ExplictObjectInfo(const QUrl &typeIri, const QUrl &objectIri,
187                       const QTrackerContactDetail &detail)
188         : d(new ExplictObjectInfoData(typeIri, objectIri, detail))
189     {
190     }
191
192 public: // operators
193     ExplictObjectInfo(const ExplictObjectInfo &other)
194         : d(other.d)
195     {
196     }
197
198     ExplictObjectInfo & operator=(const ExplictObjectInfo &other)
199     {
200         return d = other.d, *this;
201     }
202
203 public: // attributes
204     const QString & typeIri() const { return d->m_typeIri; }
205     const QString & objectIri() const { return d->m_objectIri; }
206     const QStringList & predicateIris() const { return d->m_predicateIris; }
207     const QStringList & subTypeIris() const { return d->m_subTypeIris; }
208     bool hasCustomFields() const { return d->m_hasCustomFields; }
209
210 private: // fields
211     QExplicitlySharedDataPointer<ExplictObjectInfoData> d;
212 };
213
214 ///////////////////////////////////////////////////////////////////////////////////////////////////
215
216 class UpdateBuilder
217 {
218 public: // constructors
219     UpdateBuilder(const QTrackerContactSaveRequest *request, const QContact &contact);
220
221 public: // attributes
222     const QContactGuid & guidDetail() const { return m_guidDetail; }
223     bool hadGuid() const { return m_hadGuid; }
224
225     const QDateTime & created() const { return m_created; }
226     bool hadCreated() const { return m_hadCreated; }
227
228     const QDateTime & accessed() const { return m_accessed; }
229     const QDateTime & lastModified() const { return m_lastModified; }
230
231     const DetailMappingList & detailMappings() const { return m_detailMappings; }
232     const QContactLocalId & contactLocalId() const { return m_contactLocalId; }
233     const QTrackerContactDetailSchema & schema() const { return m_schema; }
234     const QTrackerClassHierarchy & classes() const { return m_classes; }
235
236     bool isSelfContact() const { return m_isSelfContact; }
237
238     QString queryString();
239
240 protected: // attributes
241     const QctLogger & qctLogger() const { return m_logger; }
242
243 protected: // methods
244     void collectInsertions();
245
246     QString makeUniqueName(const QString &basename = QLatin1String("_"));
247
248     RDFVariable & lookupAffiliation(const QString &context);
249
250     void restrictAffiliation(const RDFVariable &contact,
251                              const RDFVariable &affiliation,
252                              const QString &context);
253
254     void insert(const RDFVariable &subject, const RDFVariable &predicate,
255                 const RDFVariable &value, bool explicitValue = true);
256
257     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
258                             const QString &fieldName, const QVariant &value,
259                             const QVariantList &allowableValues = QVariantList());
260     void insertCustomValues(const RDFVariable &subject, const QString &detailName,
261                             const QTrackerContactDetailField &field, const QVariant &value);
262
263     void insertCustomDetail(RDFVariable &subject, const DetailMapping &detail);
264
265     void insertDetailField(QContactLocalId contactId,
266                            const RDFVariable &subject, const DetailMapping &detail,
267                            const QTrackerContactDetailField &field, const QVariant &value,
268                            const EntityHash &entities, PredicateVariableHash &objectCache);
269
270     void appendPredicateChain(const QString &prefix, const PredicateChain &chain,
271                               const QString &target, const QString &suffix);
272
273     void collectRelatedObjects();
274
275     void deleteCustomProperties();
276     void deleteRelatedObjects();
277     void deleteContactProperties();
278
279     void insertForeignKeyObjects();
280
281 private: // fields
282     const QctLogger &m_logger;
283     const QTrackerContactDetailSchema &m_schema;
284     const QTrackerClassHierarchy &m_classes;
285     const QUrl &m_graphIri;
286
287     QContactGuid m_guidDetail;
288     QDateTime m_accessed;
289     QDateTime m_created;
290     QDateTime m_lastModified;
291     DetailMappingList m_detailMappings;
292     QContactLocalId m_contactLocalId;
293     QString m_contactIri;
294
295     AffiliationMap m_affiliations;
296
297     bool m_hadGuid : 1;
298     bool m_hadCreated : 1;
299     bool m_isSelfContact : 1;
300
301     QList<QTrackerContactDetail> m_implictlyRelatedObjects;
302     ExplictObjectInfoListMap m_explicitlyRelatedObjects;
303
304     RDFStatementList m_explicitInsertStatements;
305     RDFStatementList m_implicitInsertStatements;
306
307     QString m_queryString;
308     int m_variableCounter;
309 };
310
311 ///////////////////////////////////////////////////////////////////////////////////////////////////
312
313 class QTrackerContactSaveRequestTask : public QObject
314 {
315     Q_DISABLE_COPY(QTrackerContactSaveRequestTask);
316     Q_OBJECT;
317
318 public:
319     QTrackerContactSaveRequestTask(const QDBusPendingCall &call, int offset, QObject *parent = 0);
320
321 signals:
322     void success(int offset);
323     void failure(int offset, QString const &message);
324
325 private slots:
326     void finished();
327
328 private:
329     QDBusPendingCallWatcher m_call;
330     const int m_offset;
331 };
332
333 ///////////////////////////////////////////////////////////////////////////////////////////////////
334
335 template <typename T>
336 inline bool operator<(const QList<T> &a, const QList<T> &b)
337 {
338     for(int i = 0; i < a.count() && i < b.count(); ++i) {
339         if (a[i] < b[i]) {
340             return true;
341         } else if (b[i] < a[i]) {
342             return false;
343         }
344     }
345
346     return a.count() < b.count();
347 }
348
349 ///////////////////////////////////////////////////////////////////////////////////////////////////
350
351 inline bool
352 DetailMapping::isCustomDetail() const
353 {
354     return (0 == d->m_trackerDetail);
355 }
356
357 inline bool
358 DetailMapping::isSyntheticDetail() const
359 {
360     return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthetic());
361 }
362
363 inline QStringList
364 DetailMapping::contexts() const
365 {
366     if (0 != d->m_trackerDetail && not d->m_trackerDetail->hasContext()) {
367         return QStringList();
368     }
369
370     if (not d->m_contextsNormalized) {
371         QStringList normalizedContexts;
372
373         foreach(const QString &context, d->m_genericDetail.contexts().toSet()) {
374             normalizedContexts += qctCamelCase(context);
375         }
376
377         d->m_genericDetail.setContexts(normalizedContexts);
378         d->m_contextsNormalized = true;
379     }
380
381     return d->m_genericDetail.contexts();
382 }
383
384 inline QString
385 DetailMapping::definitionName() const
386 {
387     return d->m_genericDetail.definitionName();
388 }
389
390 inline QString
391 DetailMapping::detailUri() const
392 {
393     return d->m_genericDetail.detailUri();
394 }
395
396 inline QStringList
397 DetailMapping::linkedDetailUris() const
398 {
399     return d->m_genericDetail.linkedDetailUris();
400 }
401
402 inline QVariant
403 DetailMapping::fieldValue(const QString &fieldName) const
404 {
405     return d->m_genericDetail.variantValue(fieldName);
406 }
407
408 inline QVariantMap
409 DetailMapping::fieldValues() const
410 {
411     return d->m_genericDetail.variantValues();
412 }
413
414 inline const QList<QTrackerContactDetailField> &
415 DetailMapping::trackerFields() const
416 {
417     if (0 == d->m_trackerDetail) {
418         static const QList<QTrackerContactDetailField> nothing;
419         return nothing;
420     }
421
422     return d->m_trackerDetail->fields();
423 }
424
425 inline const QTrackerContactDetailField *
426 DetailMapping::subTypeField() const
427 {
428     return (0 != d->m_trackerDetail ? d->m_trackerDetail->subTypeField() : 0);
429 }
430
431 inline QTrackerContactSubject::Scheme
432 DetailMapping::detailUriScheme() const
433 {
434     return (0 != d->m_trackerDetail ? d->m_trackerDetail->detailUriScheme()
435                                     : QTrackerContactSubject::None);
436 }
437
438 QUrl
439 DetailMapping::makeResourceIri(QContactLocalId contactId) const
440 {
441     QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::None;
442     QVariantList values;
443
444     // build new detail URI from current detail content
445     foreach(const QTrackerContactDetailField &field, trackerFields()) {
446         PropertyInfoList::ConstIterator detailUriProperty = field.detailUriProperty();
447
448         if (detailUriProperty == field.propertyChain().end()) {
449             continue;
450         }
451
452         // don't mess with read only fields
453         if (field.propertyChain().first().isReadOnly()) {
454             continue;
455         }
456
457         // get schema from very first detail URI field
458         if (values.isEmpty()) {
459             scheme = detailUriProperty->resourceIriScheme();
460
461             if (QTrackerContactSubject::None == scheme) {
462                 qctWarn(QString::fromLatin1("detail URI property of %1 detail's %2 field "
463                                             "doesn't have an IRI scheme assigned").
464                         arg(definitionName(), field.name()));
465                 continue;
466             }
467         }
468
469         // collect value of this detail URI field
470         values.append(fieldValue(field.name()));
471     }
472
473     // detail URI doesn't use field values, abort
474     if (values.isEmpty()) {
475         return QUrl();
476     }
477
478     return QTrackerContactSubject::makeIri(scheme, contactId, values);
479 }
480
481 bool
482 DetailMapping::updateDetailUri(QContactLocalId contactId, const DetailMappingList &mappings)
483 {
484     // skip custom details
485     if (isCustomDetail()) {
486         return false;
487     }
488
489     const QString oldDetailUri = detailUri();
490     const QString newDetailUri = makeResourceIri(contactId).toString();
491
492     // abort if nothing to see
493     if (newDetailUri.isEmpty() || newDetailUri == oldDetailUri) {
494         return false;
495     }
496
497     // update detail URI
498     d->m_genericDetail.setDetailUri(newDetailUri);
499
500     // update details pointing to this one
501     foreach(const DetailMapping &otherDetail, mappings) {
502         QStringList linkedDetails = otherDetail.linkedDetailUris();
503
504         // try to remove old URI from list of linked details
505         if (linkedDetails.removeOne(oldDetailUri)) {
506             // detail was linked, therefore update the link
507             otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
508         }
509     }
510
511     return true;
512 }
513
514 ////////////////////////////////////////////////////////////////////////////////////////////////////
515
516 QTrackerContactSaveRequestTask::QTrackerContactSaveRequestTask(const QDBusPendingCall &call,
517                                                                int offset, QObject *parent)
518     : QObject(parent)
519     , m_call(call)
520     , m_offset(offset)
521 {
522     connect(&m_call, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(finished()));
523 }
524
525 void
526 QTrackerContactSaveRequestTask::finished()
527 {
528     if (m_call.isError()) {
529         emit failure(m_offset, m_call.error().message());
530     } else {
531         emit success(m_offset);
532     }
533 }
534
535 ///////////////////////////////////////////////////////////////////////////////////////////////////
536
537 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
538                                                        QContactTrackerEngine *engine,
539                                                        QObject *parent) :
540     QTrackerBaseRequest<QContactSaveRequest>(request, engine, parent),
541     m_timestamp(QDateTime::currentDateTime()),
542     m_effectiveError(QContactManager::UnspecifiedError),
543     m_contactOffset(0), m_batchSize(0)
544 {
545     m_contacts.append(m_request->contacts());
546 }
547
548 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
549 {
550 }
551
552 ///////////////////////////////////////////////////////////////////////////////////////////////////
553
554 static void
555 addEntity(const QTrackerClassHierarchy &classes, EntityHash &entities,
556           const PredicateChain &predicates, const QUrl &newIri)
557 {
558     foreach(const QUrl *const oldIri, entities.values(predicates)) {
559         if (classes.isSubClassOf(*oldIri, newIri)) {
560             // skipping already covered entity type
561             return;
562         }
563
564         if (classes.isSubClassOf(newIri, *oldIri)) {
565             // removing too generic entity type which is covered by newIri
566             entities.remove(predicates, oldIri);
567         }
568     }
569
570     // adding the new entity type
571     entities.insertMulti(predicates, &newIri);
572 }
573
574 static EntityHash
575 collectEntities(const QTrackerClassHierarchy &classes, const DetailMapping &detail)
576 {
577     EntityHash entities;
578
579     foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
580         if (field.propertyChain().isEmpty()) {
581             continue;
582         }
583
584         PredicateChain predicates;
585
586         const PropertyInfoList::ConstIterator end(field.propertyChain().end() - 1);
587         for(PropertyInfoList::ConstIterator i(field.propertyChain().begin()); i != end; ++i) {
588             const QUrl &predicateIri(i->iri());
589             const PropertyInfoBase &pi(*(i + 1));
590
591             if (pi.isInverse()) {
592                 addEntity(classes, entities, predicates << predicateIri, pi.rangeIri());
593             } else {
594                 addEntity(classes, entities, predicates << predicateIri, pi.domainIri());
595             }
596         }
597
598         if (field.hasSubTypeClasses()) {
599             QVariant subTypeValue(detail.fieldValue(field.name()));
600             QSet<QString> subTypes;
601
602             if (subTypeValue.isNull() && not field.defaultValue().isNull()) {
603                 subTypeValue = field.defaultValue();
604             }
605
606             switch(subTypeValue.type()) {
607             case QVariant::String:
608                 subTypes.insert(subTypeValue.toString());
609                 break;
610
611             case QVariant::StringList:
612                 subTypes.unite(subTypeValue.toStringList().toSet());
613                 break;
614
615             default:
616                 qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %3 of %2 detail").
617                         arg(subTypeValue.typeName(), detail.definitionName(), field.name()));
618                 continue;
619             }
620
621             predicates.append(field.propertyChain().last().iri());
622
623             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
624                 if (subTypes.contains(i.text())) {
625                     addEntity(classes, entities, predicates, i.iri());
626                 }
627             }
628         }
629     }
630
631     return entities;
632 }
633
634 static QVariant
635 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
636 {
637     QVariant value;
638
639     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
640         value = detail.fieldValue(subTypeField->name());
641
642         if (not value.isNull() && QVariant::String != value.type()) {
643             qctWarn(QString::fromLatin1("Invalid value type %1 for subtype field %3 of %2 detail").
644                     arg(value.typeName(), detail.definitionName(), subTypeField->name()));
645             value = QVariant();
646         }
647     }
648
649     return value;
650
651 }
652
653 static const QUrl &
654 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
655                      const QTrackerContactDetailField *const subTypeField)
656 {
657     const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
658
659     if (subTypeValue.isValid()) {
660         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
661             if (pi.value() == subTypeValue) {
662                 return pi.iri();
663             }
664         }
665     }
666
667     return field.propertyChain().last().iri();
668 }
669
670 QString
671 UpdateBuilder::makeUniqueName(const QString &basename)
672 {
673     // Apply crazy numeric padding to stop QtTracker from reordering blank nodes.
674     // As a result custom string list values get subsequent tracker ids and
675     // we can avoid any additional measure to keep list values ordered.
676     return QString::fromLatin1("%1%2").arg(basename).
677             arg(m_variableCounter++, 9, 10, QChar('0'));
678 }
679
680 RDFVariable &
681 UpdateBuilder::lookupAffiliation(const QString &context)
682 {
683     AffiliationMap::Iterator affilination = m_affiliations.find(context);
684
685     // create nco::Affiliation entity when needed
686     if (affilination == m_affiliations.end()) {
687         const QString baseName = QTrackerContactQueryBuilder::name(QLatin1String("Affiliation"), context);
688         affilination = m_affiliations.insert(context, RDFVariable(makeUniqueName(baseName)));
689    }
690
691     return *affilination;
692 }
693
694 void
695 UpdateBuilder::insertCustomValues(const RDFVariable &subject,
696                                   const QString &detailName, const QString &fieldName,
697                                   const QVariant &value, const QVariantList &allowableValues)
698 {
699     QVariantList customValues;
700
701     switch(value.type()) {
702     case QVariant::StringList:
703         foreach(const QString &element, value.toStringList()) {
704             if (not element.isEmpty() && not allowableValues.contains(element)) {
705                 customValues.append(element);
706             }
707         }
708
709         break;
710
711     case QVariant::List:
712         foreach(const QVariant &element, value.toList()) {
713             if (not element.isNull() && not allowableValues.contains(element)) {
714                 customValues.append(element);
715             }
716         }
717
718         break;
719
720     default:
721         if (not value.isNull() && not allowableValues.contains(value)) {
722             customValues.append(value);
723         }
724     }
725
726     foreach(const QVariant &element, customValues) {
727         RDFVariable property(makeUniqueName(QTrackerContactQueryBuilder::name(detailName, fieldName)));
728
729         insert(subject, rdf::type::iri(), nie::InformationElement::iri());
730         insert(subject, nao::hasProperty::iri(), property);
731
732         insert(property, rdf::type::iri(), nao::Property::iri());
733         insert(property, nao::propertyName::iri(), LiteralValue(fieldName));
734         insert(property, nao::propertyValue::iri(), QTrackerContactQueryBuilder::makeObject(element));
735     }
736 }
737
738 void
739 UpdateBuilder::insertCustomValues(const RDFVariable &subject, const QString &detailName,
740                                   const QTrackerContactDetailField &field,
741                                   const QVariant &value)
742 {
743     insertCustomValues(subject, detailName, field.name(), value,
744                        field.describeAllowableValues());
745 }
746
747 static PropertyInfoList::ConstIterator
748 endOfPropertyChain(const QTrackerContactDetailField &field)
749 {
750     if (field.isWithoutMapping()) {
751         return field.propertyChain().end();
752     }
753
754     return field.propertyChain().end() - 1;
755 }
756
757 void
758 UpdateBuilder::insertDetailField(QContactLocalId contactId,
759                                  const RDFVariable &subject, const DetailMapping &detail,
760                                  const QTrackerContactDetailField &field, const QVariant &value,
761                                  const EntityHash &entities, PredicateVariableHash &objectCache)
762 {
763     if (not value.isValid()) {
764         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
765                 arg(detail.definitionName(), field.name()));
766         return;
767     }
768
769     const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
770     const PropertyInfoList &propertyChain = field.propertyChain();
771
772     if (propertyChain.isEmpty()) {
773         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
774                 arg(detail.definitionName(), field.name()));
775         return;
776     }
777
778     if (propertyChain.first().isReadOnly()) {
779         return;
780     }
781
782     PredicateChain predicateChain;
783     RDFVariableList axis;
784     axis.append(subject);
785
786     const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
787     const PropertyInfoList::ConstIterator tail = propertyChain.end() - 1;
788
789     for(PropertyInfoList::ConstIterator pi = propertyChain.begin(); pi != end; ++pi) {
790         if (pi->isReadOnly()) {
791             break;
792         }
793
794         PredicateVariableHash::ConstIterator object = objectCache.find(predicateChain << pi->iri());
795
796         if (object == objectCache.end()) {
797             // figure out subject IRI for this field
798             QUrl subjectIri;
799
800             const PropertyInfoList::ConstIterator npi = pi + 1;
801
802             if (npi != propertyChain.end()) {
803                 const QTrackerContactSubject::Scheme resourceIriScheme = npi->resourceIriScheme();
804
805                 if (QTrackerContactSubject::isContentScheme(resourceIriScheme)) {
806                     // Prefer the detail's explicit URI for tail nodes, but only if the
807                     // property's subject scheme matches the detail's scheme. This additional
808                     // check is needed because details like Organization randomly spread their
809                     // fields over many different entities.
810                     if (npi == tail && resourceIriScheme == detail.detailUriScheme()) {
811                         subjectIri = detail.detailUri();
812                     }
813
814                     // If we don't have an entity IRI yet generate it from property and value.
815                     if (subjectIri.isEmpty()) {
816                         subjectIri = npi->makeResourceIri(contactId, value);
817                     }
818                 }
819             }
820
821             // name object variable and assign subject IRI
822             RDFVariable objectVariable;
823
824             if (subjectIri.isEmpty()) {
825                 // create named blank variable if no subject IRI could be built
826                 const QString basename = QTrackerContactQueryBuilder::
827                         name(detail.definitionName(), QLatin1String("Resource"));
828                 objectVariable.metaSetIdentifier(makeUniqueName(basename));
829             } else {
830                 // assign generated subject IRI
831                 objectVariable.metaAssign(subjectIri);
832             }
833
834             // assign RDF classes to this field
835             EntityHash::ConstIterator entity = entities.find(predicateChain);
836
837             if (entities.end() == entity) {
838                 qctWarn(QString::fromLatin1("Cannot find entities for %3 property of %1 detail's %2 field").
839                         arg(detail.definitionName(), field.name(), predicateChain.last().toString()));
840             }
841
842             for(; entity != entities.end() && entity.key() == predicateChain; ++entity) {
843                 // create insert statement of this entity type
844                 object = objectCache.insert(predicateChain, objectVariable);
845                 insert(*object, rdf::type::iri(), **entity);
846             }
847         }
848
849         // create insert statement with the field's value
850         if (pi->isInverse()) {
851             insert(*object, pi->iri(), axis.last());
852         } else {
853             insert(axis.last(), pi->iri(), *object);
854         }
855
856         axis.append(*object);
857
858         // insert custom values for subTypes field
859         if (subTypeField && subTypeField->permitsCustomValues()) {
860             insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
861                                detail.fieldValue(subTypeField->name()));
862         }
863     }
864
865     // find proper type of the value to insert
866     if (not field.isWithoutMapping()) {
867         const QUrl subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
868         const RDFVariable valueObject = QTrackerContactQueryBuilder::makeObject(value);
869         const PropertyInfoBase & valueProperty = field.propertyChain().last();
870
871         // store field value
872         if (valueProperty.isInverse()) {
873             insert(valueObject, subTypePredicate, axis.last());
874         } else {
875             insert(axis.last(), subTypePredicate, valueObject);
876         }
877
878         // restrict object variable if current property describes a foreign key
879         if (valueProperty.isForeignKey()) {
880             switch(valueProperty.caseSensitivity()) {
881             case Qt::CaseSensitive:
882                 valueProperty.bindProperty(axis.last(), valueObject);
883                 break;
884
885             case Qt::CaseInsensitive:
886                 valueProperty.
887                         bindProperty(axis.last()).function(QTrackerContactQueryBuilder::fnLowerCase).
888                         equal(valueObject.function(QTrackerContactQueryBuilder::fnLowerCase));
889                 break;
890             }
891         }
892     }
893
894     // insert computed values
895     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
896         QVariant computedValue;
897
898         if (pi.conversion()->makeValue(value, computedValue)) {
899             insert(axis.last(), pi.iri(), QTrackerContactQueryBuilder::makeObject(computedValue));
900         }
901     }
902
903     // store custom values
904     if (field.permitsCustomValues()) {
905         insertCustomValues(axis.last(), detail.definitionName(), field, value);
906     }
907 }
908
909 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request, const QContact &contact)
910     : m_logger(request->qctLogger())
911     , m_schema(request->engine()->schema(contact.type()))
912     , m_classes(request->engine()->classes())
913     , m_graphIri(request->engine()->graphIri())
914     , m_variableCounter(1)
915 {
916     // figure out the contact's timestamps
917    const QContactTimestamp timestampDetail = contact.detail<QContactTimestamp>();
918
919    m_accessed = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
920    m_created = timestampDetail.created();
921    m_lastModified = timestampDetail.lastModified();
922
923    if (m_accessed.isNull()) {
924        m_accessed = request->timestamp();
925    }
926
927    if (m_created.isNull()) {
928        m_created = request->timestamp();
929        m_hadCreated = false;
930    } else {
931        m_hadCreated = true;
932    }
933
934    if (m_lastModified.isNull() || 0 != contact.localId()) {
935        // only preserve the lastModified field for new contacts...
936        m_lastModified = request->timestamp();
937    }
938
939    // figure out the contact's GUID
940    m_guidDetail = contact.detail<QContactGuid>();
941    m_hadGuid = not m_guidDetail.guid().isEmpty();
942
943    if (not m_hadGuid) {
944        m_guidDetail = request->engine()->guidAlgorithm().makeGuid(contact);
945    }
946
947    // calculate local contact ID
948    m_contactLocalId = contact.localId();
949
950    if (0 == m_contactLocalId) {
951        m_contactLocalId = qHash(guidDetail().guid());
952    }
953
954    m_isSelfContact = (m_contactLocalId == request->engine()->selfContactId(0));
955    m_contactIri = schema().makeContactIri(contactLocalId()).toString();
956
957    // collect details and update their URIs
958    foreach(const QContactDetail &detail, contact.details()) {
959         const QTrackerContactDetail *definition(m_schema.detail(detail.definitionName()));
960
961         if (0 == definition) {
962             if (m_schema.isSyntheticDetail(detail.definitionName())) {
963                 continue; // don't even store synthesized details as nao:Property
964             }
965         }
966
967         m_detailMappings.append(DetailMapping(detail, definition));
968     }
969
970    foreach(DetailMapping detail, m_detailMappings) {
971        detail.updateDetailUri(contactLocalId(), m_detailMappings);
972    }
973 }
974
975 void
976 UpdateBuilder::insert(const RDFVariable &subject, const RDFVariable &predicate,
977                       const RDFVariable &value, bool explicitValue)
978 {
979     if (explicitValue) {
980         m_explicitInsertStatements += RDFStatement(subject, predicate, value);
981     } else {
982         RDFVariable restricted = subject.metaValue().iri();
983         RDFVariable property = restricted.optional().property(predicate);
984
985         m_implicitInsertStatements += RDFStatement(restricted, predicate, value);
986
987         not restricted.variable(property).isBound();
988     }
989 }
990
991 void
992 UpdateBuilder::insertCustomDetail(RDFVariable &subject, const DetailMapping &detail)
993 {
994     const QString detailName = detail.definitionName();
995     const QVariantMap fields = detail.fieldValues();
996
997     RDFVariable detailProperty(makeUniqueName(detailName));
998
999     insert(subject, nao::hasProperty::iri(), detailProperty);
1000     insert(detailProperty, rdf::type::iri(), nao::Property::iri());
1001     insert(detailProperty, nao::propertyName::iri(), LiteralValue(detailName));
1002
1003     for(QVariantMap::ConstIterator i = fields.begin(); i != fields.end(); ++i) {
1004         insertCustomValues(detailProperty, detailName, i.key(), i.value());
1005     }
1006 }
1007
1008 void
1009 UpdateBuilder::restrictAffiliation(const RDFVariable &contact,
1010                                    const RDFVariable &affiliation,
1011                                    const QString &context)
1012 {
1013     insert(affiliation, rdf::type::iri(), nco::Affiliation::iri());
1014     insert(affiliation, rdfs::label::iri(), LiteralValue(context));
1015     insert(contact, nco::hasAffiliation::iri(), affiliation);
1016 }
1017
1018 void
1019 UpdateBuilder::collectInsertions()
1020 {
1021     m_explicitInsertStatements.clear();
1022     m_implicitInsertStatements.clear();
1023
1024     // Removing Role properties also removed the contact's PersonContact type,
1025     // so we have to restore it.
1026     RDFVariable contact = schema().makeContactIri(contactLocalId());
1027
1028     // Restore the resource type, we removed it for quick propertly deletion.
1029     foreach(const QUrl &classIri, schema().contactClassIris()) {
1030         insert(contact, rdf::type::iri(), classIri);
1031     }
1032
1033     // Always update the local UID to support a-priori contacts like the self contact.
1034     // Accept update errors if that property already exists with different value as
1035     // this property is assumed to be immutable.
1036     const LiteralValue contactLocalUID(QString::number(contactLocalId()));
1037     insert(contact, nco::contactLocalUID::iri(), contactLocalUID);
1038
1039     // Prepare affiliation for work context in case the contact has an organization detail.
1040     // This one should be stored with the same affiliation as the work context.
1041     RDFVariable affiliation = lookupAffiliation(QContactDetail::ContextWork);
1042
1043     // Collect inserts for each regular detail.
1044     foreach(const DetailMapping &detail, detailMappings()) {
1045         // Store custom details via nao:Property.
1046         if (detail.isCustomDetail()) {
1047             insertCustomDetail(contact, detail);
1048             continue;
1049         }
1050
1051         // Don't save synthesized details.
1052         if (detail.isSyntheticDetail()) {
1053             continue;
1054         }
1055
1056         const QVariantMap detailValues = detail.fieldValues();
1057         const EntityHash entities = collectEntities(classes(), detail);
1058         QStringList detailContexts = detail.contexts();
1059         PredicateVariableHash objectCache;
1060
1061         const bool hasOtherContext =
1062                 (detailContexts.isEmpty() ||
1063                  detailContexts.removeAll(QContactDetail::ContextOther) > 0);
1064
1065         // Add the prepared work context's affiliation to object cache
1066         // to make sure it will be used when an organization detail would be used.
1067         objectCache.insert(PredicateChain() << nco::hasAffiliation::iri(), affiliation);
1068
1069         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
1070             if (field.hasSubTypes()) {
1071                 // Subtypes are stored indirectly by assigning the proper resource class
1072                 // to the RDF resource representing this field, or by selecting the matching
1073                 // RDF property for storing the detail value.
1074                 continue;
1075             }
1076
1077             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
1078
1079             if (fieldValue == detailValues.end()) {
1080                 // Could not find value for this field.
1081                 continue;
1082             }
1083
1084             QVariant rdfValue;
1085
1086             if (not field.makeValue(*fieldValue, rdfValue)) {
1087                 // Could not compute the RDF value for this field.
1088                 continue;
1089             }
1090
1091             if (hasOtherContext) {
1092                 insertDetailField(contactLocalId(), contact, detail, field,
1093                                   rdfValue, entities, objectCache);
1094             }
1095
1096             foreach(const QString &context, detailContexts) {
1097                 RDFVariable contextAffiliation = lookupAffiliation(context);
1098                 restrictAffiliation(contact, contextAffiliation, context);
1099
1100                 insertDetailField(contactLocalId(), contextAffiliation,
1101                                   detail, field, rdfValue, entities, objectCache);
1102             }
1103         }
1104     }
1105
1106     // Check if the prepared affiliation was used for binding contact details.
1107     // In that case this prepare affiliation must be turned into a proper one.
1108     foreach(const RDFStatement &statement, m_explicitInsertStatements) {
1109         if (statement.subject().metaIdentifier() == affiliation.metaIdentifier()) {
1110             restrictAffiliation(contact, statement.subject(), QContactDetail::ContextWork);
1111             break;
1112         }
1113     }
1114
1115     // Insert timestamps and such.
1116     insert(contact, nie::contentLastModified::iri(), LiteralValue(lastModified()));
1117     insert(contact, nie::contentCreated::iri(), LiteralValue(created()), hadCreated());
1118     insert(contact, nco::contactUID::iri(), LiteralValue(guidDetail().guid()), hadGuid());
1119
1120     // Add prepared INSERT statements to the query string.
1121     RDFUpdate update;
1122
1123     update.addInsertion(m_explicitInsertStatements, m_graphIri);
1124
1125     foreach(const RDFStatement &statement, m_implicitInsertStatements) {
1126         update.addInsertion(statement, m_graphIri);
1127     }
1128
1129     m_queryString += BackEnds::Tracker::tracker()->rawQueryString(update);
1130 }
1131
1132 void
1133 UpdateBuilder::collectRelatedObjects()
1134 {
1135     m_implictlyRelatedObjects.clear();
1136     m_explicitlyRelatedObjects.clear();
1137
1138     foreach(const QTrackerContactDetail &detail, schema().details()) {
1139         const QTrackerContactDetailField *const subjectField = detail.resourceIriField();
1140
1141         if (0 != subjectField) {
1142             const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1143             const PropertyInfoBase &subjectProperty = *subjectField->detailUriProperty();
1144
1145             if (subjectPropertyChain.first().isReadOnly()) {
1146                 continue;
1147             }
1148
1149             if (QTrackerContactSubject::isContentScheme(subjectProperty.resourceIriScheme())) {
1150                 // delete related objects by their content IRI
1151                 foreach(const DetailMapping &mapping, detailMappings()) {
1152                     if (mapping.definitionName() != detail.name()) {
1153                         continue;
1154                     }
1155
1156                     const QUrl objectIri = mapping.makeResourceIri(contactLocalId());
1157
1158                     if (objectIri.isEmpty()) {
1159                         qctWarn(QString::fromLatin1("Empty object IRI for %1 detail which "
1160                                                     "has a content IRI").arg(detail.name()));
1161                         continue;
1162                     }
1163
1164                     const QString typeIri = subjectProperty.resourceTypeIri().toString();
1165
1166                     m_explicitlyRelatedObjects[detail.name()] +=
1167                             ExplictObjectInfo(typeIri, objectIri, detail);
1168                 }
1169
1170                 continue; // next detail
1171             }
1172         }
1173
1174         // no content IRI known, delete via association
1175         if (not detail.predicateChains().isEmpty()) {
1176             m_implictlyRelatedObjects += detail;
1177         }
1178     }
1179 }
1180
1181 void
1182 UpdateBuilder::appendPredicateChain(const QString &prefix, const PredicateChain &chain,
1183                                     const QString &target, const QString &suffix)
1184 {
1185     // FIXME: this method should operate on PropertyInfoList instead of PredicateChain
1186     // to properly deal with inverse predicates
1187
1188     if (not chain.isEmpty()) {
1189         PredicateChain::ConstIterator predicate = chain.begin();
1190
1191         m_queryString += prefix;
1192         m_queryString += QLatin1Char('<');
1193         m_queryString += predicate->toString();
1194         m_queryString += QLatin1String("> ");
1195
1196         while(++predicate != chain.end()) {
1197             m_queryString += QLatin1String("[ <");
1198             m_queryString += predicate->toString();
1199             m_queryString += QLatin1String("> ");
1200         }
1201
1202         m_queryString += target;
1203
1204         for(int i = chain.length(); i > 1; --i) {
1205             m_queryString += QLatin1String(" ]");
1206         }
1207
1208         m_queryString += suffix;
1209     }
1210 }
1211
1212 void
1213 UpdateBuilder::deleteCustomProperties()
1214 {
1215     // SPARQL fragments used for building the query.
1216     static const QString queryPrefix = QLatin1String
1217             ("\n"
1218 #ifndef QT_NO_DEBUG
1219              "#----------------------------------------------------------------------.\n"
1220              "# Delete custom detail fields of associated objects and custom details |\n"
1221              "#----------------------------------------------------------------------'\n"
1222 #endif // QT_NO_DEBUG
1223              "\n"
1224              "DELETE\n"
1225              "{\n"
1226              "  GRAPH <%1>\n"
1227              "  {\n"
1228              "    ?property a nao:Property, rdfs:Resource .\n"
1229              "  }\n"
1230              "}\n"
1231              "WHERE\n"
1232              "{\n"
1233              "  GRAPH <%1>\n"
1234              "  {\n");
1235     static const QString implicitResourcePrefix = QLatin1String
1236             ("    {\n"
1237              "      <%1> ");
1238     static const QString implicitResourceSuffix = QLatin1String(" .\n"
1239              "    }\n"
1240              "    UNION\n");
1241     static const QString affiliatedResourcePrefix = QLatin1String
1242             ("    {\n"
1243              "      <%1> nco:hasAffiliation [ ");
1244     static const QString affiliatedResourceSuffix = QLatin1String(" ] .\n"
1245              "    }\n"
1246              "    UNION\n");
1247     static const QString querySuffix = QLatin1String
1248             ("    {\n"
1249              "      <%1> ?predicate ?resource .\n"
1250              "      FILTER(?predicate IN (<%2>)) .\n"
1251              "    }\n"
1252              "\n"
1253              "    UNION\n"
1254              "    {\n"
1255              "      ?resource a rdfs:Resource .\n"
1256              "      FILTER(?resource IN (<%3>)) .\n"
1257              "    }\n"
1258              "\n"
1259              "    ?resource nao:hasProperty ?property .\n"
1260              "  }\n"
1261              "}\n");
1262
1263     // Glue together the SPARQL fragments.
1264     m_queryString += queryPrefix.arg(m_graphIri.toString());
1265
1266     QStringList shortChains(nao::hasProperty::iri().toString());
1267
1268     foreach(const QTrackerContactDetail &detail, m_implictlyRelatedObjects) {
1269         foreach(const PredicateChain &chain, detail.customValueChains()) {
1270             if (detail.hasContext()) {
1271                 appendPredicateChain(affiliatedResourcePrefix.arg(m_contactIri),
1272                                      chain, QLatin1String("?resource"),
1273                                      affiliatedResourceSuffix);
1274             }
1275
1276             if (chain.length() > 1) {
1277                 appendPredicateChain(implicitResourcePrefix.arg(m_contactIri),
1278                                      chain, QLatin1String("?resource"),
1279                                      implicitResourceSuffix);
1280             } else {
1281                 shortChains += chain.first().toString();
1282             }
1283         }
1284     }
1285
1286     QStringList objectIris(m_contactIri);
1287
1288     foreach(const ExplictObjectInfoList &objects, m_explicitlyRelatedObjects.values()) {
1289         foreach(const ExplictObjectInfo &info, objects) {
1290             if (info.hasCustomFields()) {
1291                 objectIris += info.objectIri();
1292             }
1293         }
1294     }
1295
1296     m_queryString += querySuffix.arg(m_contactIri, shortChains.join(QLatin1String(">, <")),
1297                                      objectIris.join(QLatin1String(">, <")));
1298 }
1299
1300 void
1301 UpdateBuilder::deleteRelatedObjects()
1302 {
1303     // SPARQL fragments used for building the query.
1304     static const QString implicitQueryPrefix = QLatin1String
1305             ("\n"
1306              "DELETE\n"
1307              "{\n"
1308              "  ?resource a rdfs:Resource .\n"
1309              "}\n"
1310              "WHERE\n"
1311              "{\n");
1312     static const QString implicitResourcePrefix = QLatin1String
1313             ("  {\n"
1314              "    <%1> ");
1315     static const QString implicitResourceSuffix = QLatin1String(" .\n"
1316              "  }\n"
1317              "  UNION\n");
1318     static const QString affiliatedResourcePrefix = QLatin1String
1319             ("  {\n"
1320              "    <%1> nco:hasAffiliation [ ");
1321     static const QString affiliatedResourceSuffix = QLatin1String(" ] .\n"
1322              "  }\n"
1323              "  UNION\n");
1324     static const QString implicitQuerySuffix = QLatin1String
1325             ("  {\n"
1326              "    <%1> ?predicate ?resource .\n"
1327              "    FILTER(?predicate IN (<%2>)) ."
1328              "  }\n"
1329              "\n"
1330              "  ?resource a rdfs:Resource .\n"
1331              "}\n"
1332              "\n");
1333     static const QString explictObjectPredicateQuery = QLatin1String
1334             ("\n"
1335              "DELETE\n"
1336              "{\n"
1337              "  <%1> ?predicate ?object .\n"
1338              "}\n"
1339              "WHERE\n"
1340              "{\n"
1341              "  <%1> ?predicate ?object .\n"
1342              "  FILTER(?predicate IN (<%2>)) .\n"
1343              "}\n");
1344     static const QString explictObjectSubTypeQuery = QLatin1String
1345             ("\n"
1346              "DELETE\n"
1347              "{\n"
1348              "  ?resource a <%1> .\n"
1349              "}\n"
1350              "WHERE\n"
1351              "{\n"
1352              "  ?resource a <%2> .\n"
1353              "  FILTER(?resource IN (<%3>)) .\n"
1354              "}\n");
1355
1356     // Glue together the SPARQL fragments.
1357
1358 #ifndef QT_NO_DEBUG
1359     m_queryString += QLatin1String
1360             ("\n"
1361              "#----------------------------------------------------.\n"
1362              "# Delete associated objects via their property chain |\n"
1363              "#----------------------------------------------------'\n");
1364 #endif // QT_NO_DEBUG
1365
1366     m_queryString += implicitQueryPrefix;
1367
1368     QStringList shortChains(nao::hasProperty::iri().toString());
1369
1370     foreach(const QTrackerContactDetail &detail, m_implictlyRelatedObjects) {
1371         foreach(const PredicateChain &chain, detail.possessedChains()) {
1372             if (detail.hasContext()) {
1373                 appendPredicateChain(affiliatedResourcePrefix.arg(m_contactIri),
1374                                      chain, QLatin1String("?resource"),
1375                                      affiliatedResourceSuffix);
1376             }
1377
1378             if (chain.length() > 1) {
1379                 appendPredicateChain(implicitResourcePrefix.arg(m_contactIri),
1380                                      chain, QLatin1String("?resource"),
1381                                      implicitResourceSuffix);
1382             } else {
1383                 shortChains += chain.first().toString();
1384             }
1385         }
1386     };
1387
1388     m_queryString += implicitQuerySuffix.arg(m_contactIri, shortChains.join(QLatin1String(">, <")));
1389
1390 #ifndef QT_NO_DEBUG
1391     m_queryString += QLatin1String
1392             ("\n"
1393              "#------------------------------------------------------------.\n"
1394              "# Delete associated objects via their well known content IRI |\n"
1395              "#------------------------------------------------------------'\n");
1396 #endif // QT_NO_DEBUG
1397
1398     foreach(const ExplictObjectInfoList &objects, m_explicitlyRelatedObjects.values()) {
1399         QStringList objectIriList;
1400
1401         foreach(const ExplictObjectInfo &info, objects) {
1402             if (not info.predicateIris().isEmpty()) {
1403                 const QString predicateIris = info.predicateIris().join(QLatin1String(">, <"));
1404                 m_queryString += explictObjectPredicateQuery.arg(info.objectIri(), predicateIris);
1405             }
1406
1407             if (not info.subTypeIris().isEmpty()) {
1408                 objectIriList += info.objectIri();
1409             }
1410         }
1411
1412         if (not objects.first().subTypeIris().isEmpty()) {
1413             const QString subTypeIris = objects.first().subTypeIris().join(QLatin1String(">, <"));
1414             const QString objectIris = objectIriList.join(QLatin1String(">, <"));
1415             const QString &typeIri = objects.first().typeIri();
1416
1417             m_queryString += explictObjectSubTypeQuery.arg(subTypeIris, typeIri, objectIris);
1418         }
1419     }
1420 }
1421
1422 void
1423 UpdateBuilder::deleteContactProperties()
1424 {
1425     // SPARQL fragments used for building the query.
1426     static const QString queryPrefix = QLatin1String
1427             ("\n"
1428 #ifndef QT_NO_DEBUG
1429              "#---------------------------------------------------------.\n"
1430              "# Delete the contact properties within the plugin's graph |\n"
1431              "#---------------------------------------------------------'\n"
1432              "\n"
1433 #endif // QT_NO_DEBUG
1434              "DELETE\n"
1435              "{\n"
1436              "  GRAPH <%2>\n"
1437              "  {\n"
1438              "    <%1> ?predicate ?object .\n"
1439              "  }\n"
1440              "}\n"
1441              "WHERE\n"
1442              "{\n"
1443              "  GRAPH <%2>\n"
1444              "  {\n"
1445              "    <%1> ?predicate ?object .\n"
1446              "    FILTER(?predicate NOT IN (nco:contactLocalUID");
1447     static const QString guidFilter = QLatin1String(",nco:contactUID");
1448     static const QString createdFilter = QLatin1String(",nie:contentCreated");
1449     static const QString querySuffix = QLatin1String(",rdf:type)) .\n"
1450              "  }\n"
1451              "}\n");
1452
1453     // Glue together the SPARQL fragments.
1454     m_queryString += queryPrefix.arg(m_contactIri, m_graphIri.toString());
1455
1456     if (not hadGuid()) {
1457         // Preserve possibly existing GUID if the caller didn't provide one.
1458         m_queryString += guidFilter;
1459     }
1460
1461     if (not hadCreated()) {
1462         // Preserve possibly existing creation timestamp  if the caller didn't provide one.
1463         m_queryString += createdFilter;
1464     }
1465
1466     m_queryString += querySuffix;
1467 }
1468
1469 void
1470 UpdateBuilder::insertForeignKeyObjects()
1471 {
1472     // SPARQL fragments used for building the query.
1473     static const QString queryPrefix = QLatin1String
1474            ("\n"
1475             "INSERT\n"
1476             "{\n"
1477             "  _:_ a <%1> ; <%2> %3 .\n"
1478             "}\n"
1479             "WHERE\n"
1480             "{\n"
1481             "  OPTIONAL\n"
1482             "  {\n");
1483     static const QString caseSensitiveFilter = QLatin1String
1484            ("    ?resource a <%1> ; <%2> %3 .\n");
1485     static const QString caseInsensitiveFilter = QLatin1String
1486            ("    ?resource a <%1> ; <%2> ?value .\n"
1487             "    FILTER(fn:lower-case(%3) = fn:lower-case(?value)) .\n");
1488     static const QString querySuffix = QLatin1String
1489            ("  }\n"
1490             "\n"
1491             "  FILTER(!bound(?resource)) .\n"
1492             "}\n");
1493
1494     // Glue together the SPARQL fragments.
1495 #ifndef QT_NO_DEBUG
1496     m_queryString += QLatin1String
1497             ("\n"
1498              "#--------------------------------------------------.\n"
1499              "# Create shared objects referenced by foreign keys |\n"
1500              "#--------------------------------------------------'\n");
1501 #endif // QT_NO_DEBUG
1502
1503     foreach(const DetailMapping &detail, detailMappings()) {
1504         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
1505             const PropertyInfoList::ConstIterator pi = field.foreignKeyProperty();
1506
1507             if (field.propertyChain().end() == pi) {
1508                 continue; // skip field if no foreign key property found
1509             }
1510
1511             QVariant fieldValue = detail.fieldValue(field.name());
1512
1513             if (fieldValue.isNull()) {
1514                 continue; // skip field if no matching value stored in actual detail
1515             }
1516
1517             if (not field.makeValue(fieldValue, fieldValue)) {
1518                 continue; // skip field if value cannot be converted to storage type
1519             }
1520
1521
1522             const QString domainIri = pi->domainIri().toString();
1523             const QString predicateIri = pi->iri().toString();
1524             const QString literalValue = Soprano::Node(fieldValue).toN3();
1525
1526             if (literalValue.isEmpty()) {
1527                 qctWarn(QString::fromLatin1("Empty literal value for %2 field of %1 detail: %3.").
1528                         arg(detail.definitionName(), field.name(), fieldValue.toString()));
1529                 continue;
1530             }
1531
1532             m_queryString += queryPrefix.arg(domainIri, predicateIri, literalValue);
1533
1534             switch(pi->caseSensitivity()) {
1535             case Qt::CaseSensitive:
1536                 m_queryString += caseSensitiveFilter.arg(domainIri, predicateIri, literalValue);
1537                 break;
1538             case Qt::CaseInsensitive:
1539                 m_queryString += caseInsensitiveFilter.arg(domainIri, predicateIri, literalValue);
1540                 break;
1541             }
1542
1543             m_queryString += querySuffix;
1544         }
1545     }
1546 }
1547
1548 QString
1549 UpdateBuilder::queryString()
1550 {
1551     m_queryString.clear();
1552
1553     // collect DELETE statements
1554     collectRelatedObjects();
1555     deleteCustomProperties();
1556     deleteRelatedObjects();
1557     deleteContactProperties();
1558
1559     // collect INSERT statements
1560     insertForeignKeyObjects();
1561     collectInsertions();
1562
1563     return m_queryString;
1564 }
1565
1566 void
1567 QTrackerContactSaveRequest::normalizeCurrentContact()
1568 {
1569     QContact &contact = m_contacts[m_contactOffset];
1570
1571     foreach(const QTrackerContactDetail &detail, engine()->schema(contact.type()).details()) {
1572         if (not detail.isUnique()) {
1573             continue;
1574         }
1575
1576         QList<QContactDetail> contactDetails(contact.details(detail.name()));
1577
1578         if (contactDetails.count() < 2) {
1579             continue;
1580         }
1581
1582         qctWarn(QString::fromLatin1("Dropping odd details for contact %1: "
1583                                     "%2 detail must be unique").
1584                 arg(m_contactOffset).arg(detail.name()));
1585
1586         for(int i = 1; i < contactDetails.count(); ++i) {
1587             contact.removeDetail(&contactDetails[i]);
1588         }
1589     }
1590 }
1591
1592 static QString
1593 makeAvatarCacheFileName(const QImage& image)
1594 {
1595     const QDir avatarCacheDir = qctAvatarCacheDir();
1596
1597     // Create avatar cache directory when needed.
1598
1599     if (not avatarCacheDir.mkpath(QLatin1String("."))) {
1600         qctWarn(QString::fromLatin1("Cannot create avatar cache folder %1: %2").
1601                 arg(avatarCacheDir.path(), strerror(errno)));
1602         return QString();
1603     }
1604
1605     // Create filename from cryptographic hash of the pixel data. Probability that two
1606     // different pictures on the same device have the same hash code, is significantly
1607     // smaller than probability that we mess up because of miscalculations caused by
1608     // electrostatics and flipping bits. That's why we can apply this cheap trick.
1609
1610     const QByteArray pixels(reinterpret_cast<const char *>(image.bits()), image.byteCount());
1611     const QString pixelHash = QCryptographicHash::hash(pixels, QCryptographicHash::Sha1).toHex();
1612
1613     const QString fileName = QString::fromLatin1("%1.png").arg(pixelHash);
1614     return avatarCacheDir.absoluteFilePath(fileName);
1615 }
1616
1617 static QSet<QString>
1618 findAccountDetailUris(const QContact &contact)
1619 {
1620     QSet<QString> onlineAccountUris;
1621
1622     foreach(const QContactOnlineAccount &detail, contact.details<QContactOnlineAccount>()) {
1623         onlineAccountUris += detail.detailUri();
1624     }
1625
1626     return onlineAccountUris;
1627 }
1628
1629 static bool
1630 isLinkedDetail(const QContactDetail &detail, const QSet<QString> &linkedDetailUris)
1631 {
1632     foreach(const QString &uri, detail.linkedDetailUris()) {
1633         if (linkedDetailUris.contains(uri)) {
1634             return true;
1635         }
1636     }
1637
1638     return false;
1639 }
1640
1641 bool
1642 QTrackerContactSaveRequest::normalizeAvatars()
1643 {
1644     QContact &contact = m_contacts[m_contactOffset];
1645     const QSet<QString> accountDetailUris = findAccountDetailUris(contact);
1646
1647     // Find user supplied thumbnail of the contact itself.
1648     const QList<QContactThumbnail> thumbnailDetails = contact.details<QContactThumbnail>();
1649     QContactThumbnail defaultThumbnail;
1650
1651     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1652         qDebug()
1653                 << metaObject()->className() << m_stopWatch.elapsed()
1654                 << ": found" << thumbnailDetails.count() << "thumbnail detail(s)";
1655     }
1656
1657     foreach(const QContactThumbnail &thumbnail, thumbnailDetails) {
1658         if (not isLinkedDetail(thumbnail, accountDetailUris)) {
1659             defaultThumbnail = thumbnail;
1660             break;
1661         }
1662     }
1663
1664     if (defaultThumbnail.isEmpty()) {
1665         return true;
1666     }
1667
1668     // Find default avatar of the contact itself.
1669     QContactAvatar defaultAvatar;
1670
1671     foreach(const QContactAvatar &avatar, contact.details<QContactAvatar>()) {
1672         if (not isLinkedDetail(avatar, accountDetailUris)) {
1673             defaultAvatar = avatar;
1674             break;
1675         }
1676     }
1677
1678     // Get avatar thumbnail from detail and scale down when needed.
1679     const QSize avatarSize = QctSettings().avatarSize();
1680     QImage thumbnailImage = defaultThumbnail.thumbnail();
1681
1682     if (thumbnailImage.width() > avatarSize.width() ||
1683         thumbnailImage.height() > avatarSize.height()) {
1684         thumbnailImage = thumbnailImage.scaled(avatarSize, Qt::KeepAspectRatio);
1685     }
1686
1687     // Save the avatar image when neeed. The cached file name is based on
1688     // a cryptographic hash of the thumbnail's pixels. So if there already
1689     // exists a image file with the calculated name, there is an incredibly
1690     // high probability the existing file is equal to this current avatar.
1691     // We prefer to not write the file in this case for better performance
1692     // and for better lifetime of the storage medium.
1693     const QString avatarCacheFileName = makeAvatarCacheFileName(thumbnailImage);
1694     bool avatarWritten = true;
1695
1696     if (avatarCacheFileName.isEmpty()) {
1697         avatarWritten = false; // failed to build file name
1698     } else if (not QFile::exists(avatarCacheFileName)) {
1699         QImageWriter writer(avatarCacheFileName);
1700
1701         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1702             qDebug()
1703                     << metaObject()->className() << m_stopWatch.elapsed()
1704                     << ": writing default thumbnail:" << avatarCacheFileName;
1705         }
1706
1707         avatarWritten = writer.write(thumbnailImage);
1708
1709         if (not avatarWritten) {
1710             qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1: %2").
1711                     arg(m_contactOffset).arg(writer.errorString()));
1712         }
1713     }
1714
1715     if (avatarWritten) {
1716         // everything went find - update avatar detail and proceed
1717         defaultAvatar.setImageUrl(QUrl::fromLocalFile(avatarCacheFileName));
1718         contact.saveDetail(&defaultAvatar);
1719     } else if (not defaultAvatar.isEmpty()) {
1720         // failed to save avatar picture - remove avatar detail
1721         contact.removeDetail(&defaultAvatar);
1722     }
1723
1724     return avatarWritten;
1725 }
1726
1727 QString
1728 QTrackerContactSaveRequest::buildQuery(QContact &contact)
1729 {
1730     UpdateBuilder builder(this, contact);
1731
1732     // Create local contact id when needed
1733     QContactId id = contact.id();
1734     id.setLocalId(builder.contactLocalId());
1735     id.setManagerUri(engine()->managerUri());
1736     contact.setId(id);
1737
1738     // Build the update query
1739     return builder.queryString();
1740 }
1741
1742 ///////////////////////////////////////////////////////////////////////////////////////////////////
1743
1744 void
1745 QTrackerContactSaveRequest::saveContact()
1746 {
1747     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1748         qDebug()
1749                 << metaObject()->className() << m_stopWatch.elapsed()
1750                 << ": contact" << m_contactOffset << "- updating";
1751     }
1752
1753     // drop odd details
1754     normalizeCurrentContact();
1755
1756     // normalize and save avatar
1757     if (not normalizeAvatars()) {
1758         return;
1759     }
1760
1761     // build the update query and run it
1762     const QString queryString = buildQuery(m_contacts[m_contactOffset]);
1763
1764     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowUpdates)) {
1765         qDebug() << queryString;
1766     }
1767
1768     QDBusPendingCall updateCall = QctTracker::Resources::
1769             batchSparqlUpdate(m_trackerInstance, queryString,
1770                               engine()->trackerTimeout());
1771
1772     QTrackerContactSaveRequestTask *pendingUpdate =
1773             new QTrackerContactSaveRequestTask(updateCall, m_contactOffset, this);
1774
1775     connect(pendingUpdate, SIGNAL(failure(int,QString)), SLOT(failure(int,QString)));
1776     connect(pendingUpdate, SIGNAL(success(int)), SLOT(success(int)));
1777     m_pendingTasks.insert(m_contactOffset, pendingUpdate);
1778
1779     if (++m_batchSize >= engine()->batchSize()) {
1780         commit();
1781     }
1782 }
1783
1784 void
1785 QTrackerContactSaveRequest::proceed()
1786 {
1787     // Too many updates are running still, let's abort.
1788     // We'll get called again when next pending update is commited.
1789     if (m_pendingTasks.count() >= engine()->concurrencyLevel() / 2 &&
1790         m_pendingTasks.count() > 0) {
1791         return;
1792     }
1793
1794     while (m_contactOffset < m_contacts.count()) {
1795         // Now we have queued too many updates, let's abort.
1796         // We'll get called again when next pending update is commited.
1797         if (m_pendingTasks.count() >= engine()->concurrencyLevel()) {
1798             return;
1799         }
1800
1801         saveContact();
1802         m_contactOffset++;
1803     }
1804
1805     if (hasPendingUpdates()) {
1806         commit(); // We left the saveContact() loop, and now must commit the queued updates.
1807         return;
1808     }
1809
1810     // We left the saveContact() loop, and don't have any pending updates or
1811     // pending commits. This shows the save request was finished. Let's report this.
1812     if (not hasPendingCommit()) {
1813         if (m_errorMap.isEmpty()) {
1814             m_effectiveError = QContactManager::NoError;
1815         }
1816
1817         emitResult(m_effectiveError);
1818     }
1819 }
1820
1821 void
1822 QTrackerContactSaveRequest::commit()
1823 {
1824     // Don't do anything if another commit is running still.
1825     if (hasPendingCommit()) {
1826         return;
1827     }
1828
1829     // Dump some debug information when requested.
1830     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1831         qDebug()
1832                 << metaObject()->className() << m_stopWatch.elapsed()
1833                 << "- commiting" << m_batchSize << "pending updates";
1834     }
1835
1836     // Tell tracker to commit queued updates.
1837     QDBusPendingCall commitCall = QctTracker::Resources::
1838             batchCommit(m_trackerInstance, engine()->trackerTimeout());
1839
1840     m_commit.reset(new QDBusPendingCallWatcher(commitCall));
1841
1842     connect(m_commit.data(),
1843             SIGNAL(finished(QDBusPendingCallWatcher*)),
1844             SLOT(commitFinished()));
1845
1846     m_batchSize = 0;
1847 }
1848
1849 void
1850 QTrackerContactSaveRequest::commitFinished()
1851 {
1852     if (m_commit->isError()) {
1853         // Seems we don't have to do anything for for this error except for printing
1854         // it to console: We won't get a success signal for the failed update, and
1855         // therefore the failed contact should stay in m_errorMap[] causing proper
1856         // error reporting.
1857         qctWarn(QString::fromLatin1("Commit failed: %1").arg(m_commit->error().message()));
1858     }
1859
1860
1861     m_commit.reset(); // Mark this commit as done.
1862     proceed(); // Check if other updates are pending, or if more contacts must be written.
1863 }
1864
1865 void
1866 QTrackerContactSaveRequest::success(int offset)
1867 {
1868     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1869         qDebug()
1870                 << metaObject()->className() << m_stopWatch.elapsed()
1871                 << ": contact" << offset << " - update succeded";
1872     }
1873
1874     delete m_pendingTasks.take(offset); // Mark this update as done.
1875     m_errorMap.remove(offset); // Mark this update's contact as successfully written.
1876     proceed(); // Check if other updates are pending, or if more contacts must be written.
1877 }
1878
1879 void
1880 QTrackerContactSaveRequest::failure(int offset, QString const &message)
1881 {
1882
1883     qctWarn(QString::fromLatin1("Failed to update contact %1/%2: %3").
1884             arg(QString::number(offset + 1), QString::number(m_request->contacts().count()),
1885                 message));
1886
1887     delete m_pendingTasks.take(offset); // Mark this update as done.
1888     proceed(); // Check if other updates are pending, or if more contacts must be written.
1889
1890     // Notice: We don't remove the update's contact from m_errorMap[]
1891     // which results in proper error reporting.
1892 }
1893
1894 ///////////////////////////////////////////////////////////////////////////////////////////////////
1895
1896 bool
1897 QTrackerContactSaveRequest::hasPendingUpdates() const
1898 {
1899     return not m_pendingTasks.isEmpty();
1900 }
1901
1902 bool
1903 QTrackerContactSaveRequest::hasPendingCommit() const
1904 {
1905     return not m_commit.isNull();
1906 }
1907
1908 ///////////////////////////////////////////////////////////////////////////////////////////////////
1909
1910 bool
1911 QTrackerContactSaveRequest::start()
1912 {
1913     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
1914         qDebug()
1915                 << metaObject()->className() << 0
1916                 << ": number of contacts:" << m_request->contacts().count();
1917     }
1918
1919     m_stopWatch.start();
1920
1921     // prepare error map with default values
1922     for(int i = 0; i < m_contacts.count(); ++i) {
1923         m_errorMap.insert(i, QContactManager::UnspecifiedError);
1924     }
1925
1926     // find tracker instance
1927     m_trackerInstance = QctTracker::instance();
1928
1929     if (m_trackerInstance.isEmpty()) {
1930         qctWarn(QString::fromLatin1("tracker service not found: %1").arg(QctTracker::lastError));
1931         return false;
1932     }
1933
1934     // start operation
1935     m_contactOffset = 0;
1936
1937     engine()->guidAlgorithm().callWhenReady(this, SLOT(proceed()));
1938
1939     return true;
1940 }
1941
1942 void
1943 QTrackerContactSaveRequest::emitResult(QContactManager::Error error)
1944 {
1945     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
1946         qDebug()
1947                 << metaObject()->className()  << m_stopWatch.elapsed()
1948                 << ": reporting result" << error;
1949     }
1950
1951     engine()->updateContactSaveRequest(m_request, m_contacts, error, m_errorMap,
1952                                       QContactAbstractRequest::FinishedState);
1953 }
1954
1955 ///////////////////////////////////////////////////////////////////////////////////////////////////
1956
1957 #include "contactsaverequest.moc"