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