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