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