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