Fixes: Don't write unescaped property IRIs in save request.
[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 isExistingContact() const { return 0 != m_contactLocalId; }
307     bool isPartialSaveRequest() const { return isExistingContact() && not m_detailMask.isEmpty(); }
308     bool isUnknownDetail(const QString &name) const { return isPartialSaveRequest() && not m_detailMask.contains(name); }
309     template <class T> bool isUnknownDetail() const { return isUnknownDetail(T::DefinitionName); }
310     const QctLogger & qctLogger() const { return m_logger; }
311
312 protected: // methods
313     void collectInsertions();
314     void updateSyncTarget();
315
316     QString makeUniqueName(const QString &basename = QLatin1String("_"));
317
318     Value & lookupAffiliation(const QString &context);
319
320     enum InsertValueMode { EnforceNewValue, PreserveOldValue };
321
322     void insertValue(const Value &subject, const Value &predicate, const Value &value,
323                      InsertValueMode mode = EnforceNewValue);
324
325     void insertCustomValues(const Value &subject, const QString &detailName,
326                             const QString &fieldName, const QVariant &value,
327                             const QVariantList &allowableValues = QVariantList());
328
329     void insertCustomValues(const Value &subject, const QString &detailName,
330                             const QTrackerContactDetailField &field, const QVariant &value);
331
332     void insertCustomDetail(const Value &subject, const DetailMapping &detail);
333
334     void insertDetailField(const Value &subject, const DetailMapping &detail,
335                            const QTrackerContactDetailField &field, const QVariant &value,
336                            const ResourceTypes &types, PredicateValueHash &objectCache);
337
338     void appendPredicateChain(const QString &prefix,
339                               const PropertyInfoList::ConstIterator &begin,
340                               const PropertyInfoList::ConstIterator &end,
341                               const QString &target, const QString &suffix);
342
343     void collectRelatedObjects();
344     void deleteRelatedObjects();
345     void deleteContactProperties();
346     void deleteAllContactProperties();
347     void deleteMaskedContactProperties();
348
349     void insertForeignKeyObjects();
350
351 private: // fields
352     const QctLogger &m_logger;
353     const QTrackerContactDetailSchema &m_schema;
354     const QStringList m_detailMask;
355
356     const ResourceValue m_graphIri;
357     const QString m_graphIriSparql;
358
359     const QContactLocalId m_contactLocalId;
360     const ResourceValue m_contactIri;
361     const QString m_contactIriSparql;
362
363     QContactGuid m_guidDetail;
364     QDateTime m_lastAccessTimestamp;
365     QDateTime m_lastChangeTimestamp;
366     QDateTime m_creationTimestamp;
367     QString m_syncTarget;
368
369     DetailMappingList m_detailMappings;
370
371     AffiliationMap m_affiliations;
372
373     bool m_preserveGuid : 1;
374     bool m_preserveTimestamp : 1;
375     bool m_preserveSyncTarget : 1;
376
377     QList<QTrackerContactDetail> m_implictlyRelatedObjects;
378     QMap<QString, ExplictObjectInfoList> m_explicitlyRelatedObjects;
379     QMap<QString, ForeignObjectInfoList> m_foreignKeyRelatedObjects;
380     QList<Pattern> m_explicitInsertStatements;
381     PatternGroup m_explicitInsertRestrictions;
382     QList<Insert> m_implicitInsertStatements;
383     QString m_queryString;
384     int m_variableCounter;
385
386     const Options::SparqlOptions m_sparqlOptions;
387     const ValueList m_weakSyncTargets;
388 };
389
390 ///////////////////////////////////////////////////////////////////////////////////////////////////
391
392
393 template <typename T>
394 inline bool operator<(const QList<T> &a, const QList<T> &b)
395 {
396     for(int i = 0; i < a.count() && i < b.count(); ++i) {
397         if (a[i] < b[i]) {
398             return true;
399         } else if (b[i] < a[i]) {
400             return false;
401         }
402     }
403
404     return a.count() < b.count();
405 }
406
407 ///////////////////////////////////////////////////////////////////////////////////////////////////
408
409 inline bool
410 DetailMapping::isCustomDetail() const
411 {
412     return (0 == d->m_trackerDetail);
413 }
414
415 inline bool
416 DetailMapping::isSyntheticDetail() const
417 {
418     return (0 != d->m_trackerDetail && d->m_trackerDetail->isSynthesized());
419 }
420
421 QStringList
422 DetailMapping::contexts() const
423 {
424     if (0 != d->m_trackerDetail && not d->m_trackerDetail->hasContext()) {
425         return QStringList();
426     }
427
428     if (not d->m_contextsNormalized) {
429         QStringList normalizedContexts;
430
431         foreach(const QString &context, d->m_genericDetail.contexts().toSet()) {
432             normalizedContexts += qctCamelCase(context);
433         }
434
435         d->m_genericDetail.setContexts(normalizedContexts);
436         d->m_contextsNormalized = true;
437     }
438
439     return d->m_genericDetail.contexts();
440 }
441
442 bool
443 DetailMapping::hasContext() const
444 {
445     return (0 != d->m_trackerDetail && d->m_trackerDetail->hasContext());
446 }
447
448 inline QString
449 DetailMapping::definitionName() const
450 {
451     return d->m_genericDetail.definitionName();
452 }
453
454 inline QString
455 DetailMapping::detailUri() const
456 {
457     return d->m_genericDetail.detailUri();
458 }
459
460 inline QStringList
461 DetailMapping::linkedDetailUris() const
462 {
463     return d->m_genericDetail.linkedDetailUris();
464 }
465
466 inline QVariant
467 DetailMapping::fieldValue(const QString &fieldName) const
468 {
469     return d->m_genericDetail.variantValue(fieldName);
470 }
471
472 inline QVariantMap
473 DetailMapping::fieldValues() const
474 {
475     return d->m_genericDetail.variantValues();
476 }
477
478 inline const QList<QTrackerContactDetailField> &
479 DetailMapping::trackerFields() const
480 {
481     if (0 == d->m_trackerDetail) {
482         static const QList<QTrackerContactDetailField> nothing;
483         return nothing;
484     }
485
486     return d->m_trackerDetail->fields();
487 }
488
489 inline const QTrackerContactDetailField *
490 DetailMapping::subTypeField() const
491 {
492     return (0 != d->m_trackerDetail ? d->m_trackerDetail->subTypeField() : 0);
493 }
494
495 inline QTrackerContactSubject::Scheme
496 DetailMapping::detailUriScheme() const
497 {
498     return (0 != d->m_trackerDetail ? d->m_trackerDetail->detailUriScheme()
499                                     : QTrackerContactSubject::None);
500 }
501
502 QString
503 DetailMapping::makeResourceIri() const
504 {
505     QTrackerContactSubject::Scheme scheme = QTrackerContactSubject::None;
506     QVariantList values;
507
508     // build new detail URI from current detail content
509     foreach(const QTrackerContactDetailField &field, trackerFields()) {
510         PropertyInfoList::ConstIterator detailUriProperty = field.detailUriProperty();
511
512         if (detailUriProperty == field.propertyChain().constEnd()) {
513             continue;
514         }
515
516         // don't mess with read only fields
517         if (field.propertyChain().first().isReadOnly()) {
518             continue;
519         }
520
521         // get schema from very first detail URI field
522         if (values.isEmpty()) {
523             scheme = detailUriProperty->resourceIriScheme();
524
525             if (QTrackerContactSubject::None == scheme) {
526                 qctWarn(QString::fromLatin1("detail URI property of %1 detail's %2 field "
527                                             "doesn't have an IRI scheme assigned").
528                         arg(definitionName(), field.name()));
529                 continue;
530             }
531         }
532
533         // collect value of this detail URI field
534         values.append(fieldValue(field.name()));
535     }
536
537     // detail URI doesn't use field values, abort
538     if (values.isEmpty()) {
539         return QString();
540     }
541
542     return QTrackerContactSubject::makeIri(scheme, values);
543 }
544
545 bool
546 DetailMapping::updateDetailUri(const DetailMappingList &mappings)
547 {
548     // skip custom details
549     if (isCustomDetail()) {
550         return false;
551     }
552
553     const QString oldDetailUri = detailUri();
554     const QString newDetailUri = makeResourceIri();
555
556     // abort if nothing to see
557     if (newDetailUri.isEmpty() || newDetailUri == oldDetailUri) {
558         return false;
559     }
560
561     // update detail URI
562     d->m_genericDetail.setDetailUri(newDetailUri);
563
564     // update details pointing to this one
565     foreach(const DetailMapping &otherDetail, mappings) {
566         QStringList linkedDetails = otherDetail.linkedDetailUris();
567
568         // try to remove old URI from list of linked details
569         if (linkedDetails.removeOne(oldDetailUri)) {
570             // detail was linked, therefore update the link
571             otherDetail.d->m_genericDetail.setLinkedDetailUris(linkedDetails << newDetailUri);
572         }
573     }
574
575     return true;
576 }
577
578 ////////////////////////////////////////////////////////////////////////////////////////////////////
579
580 QTrackerContactSaveRequest::QTrackerContactSaveRequest(QContactAbstractRequest *request,
581                                                        QContactTrackerEngine *engine,
582                                                        QObject *parent)
583     : QTrackerBaseRequest<QContactSaveRequest>(engine, parent)
584     , m_contacts(staticCast(request)->contacts())
585     , m_detailMask(staticCast(request)->definitionMask())
586     , m_nameOrder(QctRequestExtensions::get(request)->nameOrder())
587     , m_timestamp(QDateTime::currentDateTime())
588     , m_batchSize(0)
589 {
590     if (not engine->mangleAllSyncTargets()) {
591         m_weakSyncTargets.addValue(LiteralValue(QString()));
592
593         foreach(const QString &target, engine->weakSyncTargets()) {
594             m_weakSyncTargets.addValue(LiteralValue(target));
595         }
596     }
597 }
598
599 QTrackerContactSaveRequest::~QTrackerContactSaveRequest()
600 {
601 }
602
603 ///////////////////////////////////////////////////////////////////////////////////////////////////
604
605 static ResourceTypes
606 collectResourceTypes(const DetailMapping &detail)
607 {
608     ResourceTypes types;
609
610     foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
611         if (field.propertyChain().isEmpty()) {
612             continue;
613         }
614
615         PropertyInfoList predicates;
616
617         const PropertyInfoList::ConstIterator end(field.propertyChain().constEnd() - 1);
618         for(PropertyInfoList::ConstIterator pi(field.propertyChain().constBegin()); pi != end; ++pi) {
619             const PropertyInfoBase &npi(*(pi + 1)); // next predicate
620
621             predicates += *pi;
622
623             if (npi.isInverse()) {
624                 types.insertMulti(predicates, npi.rangeIri());
625             } else {
626                 types.insertMulti(predicates, npi.domainIri());
627             }
628
629             if (pi->isInverse()) {
630                 types.insertMulti(predicates, pi->domainIri());
631             } else {
632                 types.insertMulti(predicates, pi->rangeIri());
633             }
634         }
635
636         QVariant fieldValue = detail.fieldValue(field.name());
637
638         if (field.hasSubTypeClasses() && not fieldValue.isNull()) {
639             QSet<QString> subTypes;
640
641             if (fieldValue.isNull() && not field.defaultValue().isNull()) {
642                 fieldValue = field.defaultValue();
643             }
644
645             switch(fieldValue.type()) {
646             case QVariant::String:
647                 subTypes.insert(fieldValue.toString());
648                 break;
649
650             case QVariant::StringList:
651                 subTypes.unite(fieldValue.toStringList().toSet());
652                 break;
653
654             default:
655                 qctWarn(QString::fromLatin1("Invalid type '%1' for subtype field %3 of %2 detail").
656                         arg(QLatin1String(fieldValue.typeName()),
657                             detail.definitionName(), field.name()));
658                 continue;
659             }
660
661             predicates.append(field.propertyChain().last());
662
663             foreach(const ClassInfoBase &i, field.subTypeClasses()) {
664                 if (subTypes.contains(i.text())) {
665                     types.insertMulti(predicates, i.iri());
666                 }
667             }
668         }
669     }
670
671     return types;
672 }
673
674 static QVariant
675 findSubTypeValue(const DetailMapping &detail, const QTrackerContactDetailField *const subTypeField)
676 {
677     QVariant value;
678
679     if (0 != subTypeField && subTypeField->hasSubTypeProperties()) {
680         value = detail.fieldValue(subTypeField->name());
681
682         if (QVariant::StringList == value.type()) {
683             // courteously pick first list element if the user ignores our detail schema
684             QStringList valueList = value.toStringList();
685
686             if (not valueList.isEmpty()) {
687                 value = valueList.first();
688             } else {
689                 value = subTypeField->defaultValue();
690             }
691
692             if (valueList.size() > 1) {
693                 qctWarn(QString::fromLatin1("Subtype field %3 of %2 detail is of type %1, "
694                                             "picking first value from supplied list.").
695                         arg(QLatin1String(QVariant::typeToName(subTypeField->dataType())),
696                             detail.definitionName(), subTypeField->name()));
697             }
698         }
699
700         // barf if the subtype value is not useful at all
701         if (not value.isNull() && QVariant::String != value.type()) {
702             qctWarn(QString::fromLatin1("Invalid value type %1 for subtype field %3 of %2 detail").
703                     arg(QLatin1String(value.typeName()), detail.definitionName(),
704                         subTypeField->name()));
705             value = QVariant();
706         }
707     }
708
709     return value;
710
711 }
712
713 static const ResourceValue
714 findSubTypePredicate(const DetailMapping &detail, const QTrackerContactDetailField &field,
715                      const QTrackerContactDetailField *const subTypeField)
716 {
717     if (subTypeField) {
718         const QVariant subTypeValue = findSubTypeValue(detail, subTypeField);
719
720         if (subTypeValue.isValid()) {
721             foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
722                 if (pi.value() == subTypeValue) {
723                     return pi.resource();
724                 }
725             }
726         }
727     }
728
729     return field.propertyChain().last().resource();
730 }
731
732 QString
733 UpdateBuilder::makeUniqueName(const QString &basename)
734 {
735     return basename + QString::number(m_variableCounter++);
736 }
737
738 static QString
739 name(const QString &prefix, const QString &suffix)
740 {
741     if (not suffix.isEmpty()) {
742         return prefix + QLatin1Char('_') + suffix;
743     }
744
745     return prefix;
746 }
747
748 Value &
749 UpdateBuilder::lookupAffiliation(const QString &context)
750 {
751     AffiliationMap::Iterator affiliation = m_affiliations.find(context);
752
753     // create nco::Affiliation resource when needed
754     if (affiliation == m_affiliations.end()) {
755         const QString baseName = name(QLatin1String("Affiliation"), context);
756         affiliation = m_affiliations.insert(context, BlankValue(makeUniqueName(baseName)));
757    }
758
759     return *affiliation;
760 }
761
762 void
763 UpdateBuilder::insertCustomValues(const Value &subject,
764                                   const QString &detailName, const QString &fieldName,
765                                   const QVariant &value, const QVariantList &allowableValues)
766 {
767     QVariantList customValues;
768
769     switch(value.type()) {
770     case QVariant::StringList:
771         foreach(const QString &element, value.toStringList()) {
772             if (not element.isEmpty() && not allowableValues.contains(element)) {
773                 customValues.append(element);
774             }
775         }
776
777         break;
778
779     case QVariant::List:
780         foreach(const QVariant &element, value.toList()) {
781             if (not element.isNull() && not allowableValues.contains(element)) {
782                 customValues.append(element);
783             }
784         }
785
786         break;
787
788     default:
789         if (not value.isNull() && not allowableValues.contains(value)) {
790             customValues.append(value);
791         }
792     }
793
794     foreach(const QVariant &element, customValues) {
795         Value property = BlankValue(makeUniqueName(name(detailName, fieldName)));
796
797         insertValue(subject, rdf::type::resource(), nie::InformationElement::resource());
798         insertValue(subject, nao::hasProperty::resource(), property);
799
800         insertValue(property, rdf::type::resource(), nao::Property::resource());
801         insertValue(property, nao::propertyName::resource(), LiteralValue(fieldName));
802         insertValue(property, nao::propertyValue::resource(), LiteralValue(element));
803     }
804 }
805
806 void
807 UpdateBuilder::insertCustomValues(const Value &subject, const QString &detailName,
808                                   const QTrackerContactDetailField &field,
809                                   const QVariant &value)
810 {
811     insertCustomValues(subject, detailName, field.name(), value,
812                        field.describeAllowableValues());
813 }
814
815 static PropertyInfoList::ConstIterator
816 endOfPropertyChain(const QTrackerContactDetailField &field)
817 {
818     if (field.isWithoutMapping() || not field.hasLiteralValue()) {
819         return field.propertyChain().constEnd();
820     }
821
822     return field.propertyChain().constEnd() - 1;
823 }
824
825 void
826 UpdateBuilder::insertDetailField(const Value &subject, const DetailMapping &detail,
827                                  const QTrackerContactDetailField &field, const QVariant &value,
828                                  const ResourceTypes &types, PredicateValueHash &objectCache)
829 {
830     if (not value.isValid()) {
831         qctWarn(QString::fromLatin1("Value must be valid for %2 field of %1 detail").
832                 arg(detail.definitionName(), field.name()));
833         return;
834     }
835
836     const QTrackerContactDetailField *const subTypeField = detail.subTypeField();
837     const PropertyInfoList &propertyChain = field.propertyChain();
838
839     if (propertyChain.isEmpty()) {
840         qctWarn(QString::fromLatin1("RDF property chain needed for %2 field of %1 detail").
841                 arg(detail.definitionName(), field.name()));
842         return;
843     }
844
845     if (propertyChain.first().isReadOnly()) {
846         return;
847     }
848
849     PropertyInfoList predicateChain;
850     QList<Value> axis;
851     axis.append(subject);
852
853     const PropertyInfoList::ConstIterator end = endOfPropertyChain(field);
854     const PropertyInfoList::ConstIterator tail = propertyChain.constEnd() - 1;
855
856     const Value literalValue = qctMakeCubiValue(value);
857     bool literalValuePending = true;
858
859     for(PropertyInfoList::ConstIterator pi = propertyChain.constBegin(); pi != end; ++pi) {
860         if (pi->isReadOnly()) {
861             break;
862         }
863
864         PredicateValueHash::ConstIterator object = objectCache.find(predicateChain << *pi);
865
866         if (object == objectCache.constEnd()) {
867             // figure out subject IRI for this field
868             QString subjectIri;
869
870             const PropertyInfoList::ConstIterator npi = pi + 1;
871
872             if (npi != propertyChain.constEnd()) {
873                 const QTrackerContactSubject::Scheme resourceIriScheme = npi->resourceIriScheme();
874
875                 if (QTrackerContactSubject::isContentScheme(resourceIriScheme)) {
876                     // Prefer the detail's explicit URI for tail nodes, but only if the
877                     // property's subject scheme matches the detail's scheme. This additional
878                     // check is needed because details like Organization randomly spread their
879                     // fields over many different resource types.
880                     if (npi == tail && resourceIriScheme == detail.detailUriScheme()) {
881                         subjectIri = detail.detailUri();
882                     }
883
884                     // If we don't have an resource IRI yet generate it from property and value.
885                     if (subjectIri.isEmpty()) {
886                         subjectIri = npi->makeResourceIri(value);
887                     }
888                 }
889             }
890
891             // name object variable and assign subject IRI
892             Value objectVariable;
893
894             if (not subjectIri.isEmpty()) {
895                 // assign generated subject IRI
896                 objectVariable = ResourceValue(subjectIri);
897             } else if (npi != propertyChain.constEnd() || field.isWithoutMapping()) {
898                 // create named blank variable if no subject IRI could be built. If the
899                 // field has no mapping, then it's saved using a nao:Property, so create
900                 // a blank node for that resource.
901                 const QString basename = name(detail.definitionName(), QLatin1String("Resource"));
902
903                 // if we're on the end of the property chanin this objectVariable
904                 // is about to become axis.last()
905                 // Now we have to check if it's a foreign value and make it a normal
906                 // variable, not a named blank variable if that's the case
907                 if ((not field.isWithoutMapping()) &&
908                     field.propertyChain().last().isForeignKey() &&
909                     (npi + 1 == propertyChain.constEnd())) {
910                     objectVariable = Variable(makeUniqueName(basename));
911                 } else {
912                     objectVariable = BlankValue(makeUniqueName(basename));
913                 }
914             } else {
915                 objectVariable = literalValue;
916                 literalValuePending = false;
917             }
918
919             // assign RDF classes to this field
920             ResourceTypes::ConstIterator fieldResource = types.find(predicateChain);
921
922             if (types.constEnd() == fieldResource) {
923                 qctWarn(QString::fromLatin1("Cannot find resource types for %3 property "
924                                             "of %1 detail's %2 field").
925                         arg(detail.definitionName(), field.name(),
926                             predicateChain.last().iri()));
927             }
928
929             for(; fieldResource != types.constEnd() && fieldResource.key() == predicateChain; ++fieldResource) {
930                 // create insert statement of this resource type
931                 object = objectCache.insert(predicateChain, objectVariable);
932                 insertValue(*object, rdf::type::resource(), ResourceValue(fieldResource.value()));
933             }
934         }
935
936         // create insert statement with the field's value
937         if (pi->isInverse()) {
938             insertValue(*object, pi->resource(), axis.last());
939         } else {
940             insertValue(axis.last(), pi->resource(), *object);
941         }
942
943         axis.append(*object);
944     }
945
946     // insert custom values for subTypes field
947     if (subTypeField && subTypeField->permitsCustomValues()) {
948         insertCustomValues(axis.last(), detail.definitionName(), *subTypeField,
949                            detail.fieldValue(subTypeField->name()));
950     }
951
952     // find proper type of the value to insert
953     if (not field.isWithoutMapping()) {
954         const ResourceValue subTypePredicate = findSubTypePredicate(detail, field, subTypeField);
955         const PropertyInfoBase & valueProperty = field.propertyChain().last();
956
957         // store field value
958         if (literalValuePending) {
959             if (valueProperty.isInverse()) {
960                 insertValue(literalValue, subTypePredicate, axis.last());
961             } else {
962                 insertValue(axis.last(), subTypePredicate, literalValue);
963             }
964         }
965
966         // restrict object variable if current property describes a foreign key
967         if (valueProperty.isForeignKey()) {
968             switch(valueProperty.caseSensitivity()) {
969             case Qt::CaseSensitive:
970                 m_explicitInsertRestrictions.addPattern(axis.last(), valueProperty.resource(), literalValue);
971                 break;
972
973             case Qt::CaseInsensitive:
974                 Variable var;
975                 PatternGroup caseInsensitiveFilter;
976                 caseInsensitiveFilter.addPattern(axis.last(), valueProperty.resource(), var);
977                 caseInsensitiveFilter.setFilter(Functions::equal.apply(Functions::lowerCase.apply(var),
978                                                                        Functions::lowerCase.apply(literalValue)));
979                 m_explicitInsertRestrictions.addPattern(caseInsensitiveFilter);
980                 break;
981             }
982         }
983     }
984
985     // insert computed values
986     foreach(const PropertyInfoBase &pi, field.computedProperties()) {
987         QVariant computedValue;
988
989         if (pi.conversion()->makeValue(value, computedValue)) {
990             insertValue(axis.last(), pi.resource(), qctMakeCubiValue(computedValue));
991         }
992     }
993
994     // store custom values
995     if (field.permitsCustomValues()) {
996         insertCustomValues(axis.last(), detail.definitionName(), field, value);
997     }
998 }
999
1000 UpdateBuilder::UpdateBuilder(const QTrackerContactSaveRequest *request,
1001                              const QContact &contact, const QString &contactIri,
1002                              const QHash<QString, QContactDetail> &detailsByUri,
1003                              const QStringList &detailMask)
1004     : m_logger(request->qctLogger())
1005     , m_schema(request->engine()->schema(contact.type()))
1006     , m_detailMask(detailMask)
1007     , m_graphIri(QtContactsTrackerDefaultGraphIri)
1008     , m_graphIriSparql(m_graphIri.sparql())
1009     , m_contactLocalId(contact.localId())
1010     , m_contactIri(contactIri)
1011     , m_contactIriSparql(m_contactIri.sparql())
1012     , m_variableCounter(1)
1013     , m_sparqlOptions(request->engine()->updateQueryOptions())
1014     , m_weakSyncTargets(request->m_weakSyncTargets)
1015 {
1016    // figure out the contact's timestamps
1017    const QContactTimestamp timestampDetail = contact.detail<QContactTimestamp>();
1018
1019    m_lastAccessTimestamp = timestampDetail.variantValue(QContactTimestamp__FieldAccessedTimestamp).toDateTime();
1020    m_lastChangeTimestamp = timestampDetail.lastModified();
1021    m_creationTimestamp = timestampDetail.created();
1022
1023    if (m_lastAccessTimestamp.isNull()) {
1024        m_lastAccessTimestamp = request->timestamp();
1025    }
1026
1027    if (m_creationTimestamp.isNull()) {
1028        m_creationTimestamp = request->timestamp();
1029        m_preserveTimestamp = (0 != m_contactLocalId); // no need to delete timestamp for new contacts
1030    } else {
1031        m_preserveTimestamp = isUnknownDetail<QContactTimestamp>();
1032    }
1033
1034    if (m_lastChangeTimestamp.isNull() || 0 != m_contactLocalId) {
1035        // only preserve the lastModified field for new contacts...
1036        m_lastChangeTimestamp = request->timestamp();
1037    }
1038
1039    // figure out the contact's GUID
1040    m_guidDetail = contact.detail<QContactGuid>();
1041
1042    if (m_guidDetail.guid().isEmpty()) {
1043        m_guidDetail = request->engine()->guidAlgorithm().makeGuid(contact);
1044        m_preserveGuid = (0 != m_contactLocalId); // no need to delete GUID for new contacts
1045    } else {
1046        m_preserveGuid = isUnknownDetail<QContactGuid>();
1047    }
1048
1049    // figure out default sync target
1050    m_syncTarget = contact.detail<QContactSyncTarget>().syncTarget();
1051
1052    if (m_syncTarget.isEmpty() || request->engine()->isWeakSyncTarget(m_syncTarget)) {
1053        m_syncTarget = request->engine()->syncTarget();
1054        m_preserveSyncTarget = false;
1055    } else {
1056        m_preserveSyncTarget = not m_weakSyncTargets.values().isEmpty();
1057    }
1058
1059    // collect details and update their URIs
1060    foreach(const QContactDetail &detail, contact.details()) {
1061        const QString detailDefinitionName = detail.definitionName();
1062
1063        // skip those not in the detail mask if given
1064        if (isUnknownDetail(detailDefinitionName)) {
1065            continue;
1066        }
1067
1068        if (detail.accessConstraints() & QContactDetail::ReadOnly) {
1069            continue; // don't mess with read-only details, e.g. those merged from social providers
1070        }
1071
1072        const QTrackerContactDetail *definition = m_schema.detail(detailDefinitionName);
1073
1074        if (0 == definition) {
1075            if (m_schema.isSyntheticDetail(detailDefinitionName)) {
1076                continue; // don't store synthesized details as nao:Property
1077            }
1078        } else {
1079            // Details like are QContactAvatar are synthetized, and must be turned into
1080            // internal details like QContactPersonalAvatar or QContactOnlineAvatar.
1081            definition = definition->findImplementation(m_schema, detailsByUri, detail);
1082        }
1083
1084        m_detailMappings.append(DetailMapping(detail, definition));
1085    }
1086
1087    foreach(DetailMapping detail, m_detailMappings) {
1088        detail.updateDetailUri(m_detailMappings);
1089    }
1090 }
1091
1092 void
1093 UpdateBuilder::insertValue(const Value &subject, const Value &predicate,
1094                            const Value &value, InsertValueMode mode)
1095 {
1096     if (mode == PreserveOldValue) {
1097         Insert insert;
1098
1099         {
1100             Graph g = Graph(m_graphIri);
1101             g.addPattern(subject, predicate, value);
1102             insert.addData(g);
1103         }
1104
1105         {
1106             Graph g = Graph(m_graphIri);
1107             g.addPattern(subject, predicate, Variable());
1108
1109             Exists exists;
1110             exists.addPattern(g);
1111
1112             PatternGroup filter;
1113             filter.setFilter(Functions::not_.apply(Filter(exists)));
1114             insert.addRestriction(filter);
1115         }
1116
1117         m_implicitInsertStatements += insert;
1118     } else {
1119         m_explicitInsertStatements.append(Pattern(subject, predicate, value));
1120     }
1121 }
1122
1123 void
1124 UpdateBuilder::insertCustomDetail(const Value &subject, const DetailMapping &detail)
1125 {
1126     const QString detailName = detail.definitionName();
1127     const QVariantMap fields = detail.fieldValues();
1128
1129     Value detailProperty = BlankValue(makeUniqueName(detailName));
1130
1131     insertValue(subject, nao::hasProperty::resource(), detailProperty);
1132     insertValue(detailProperty, rdf::type::resource(), nao::Property::resource());
1133     insertValue(detailProperty, nao::propertyName::resource(), LiteralValue(detailName));
1134
1135     for(QVariantMap::ConstIterator i = fields.constBegin(); i != fields.constEnd(); ++i) {
1136         insertCustomValues(detailProperty, detailName, i.key(), i.value());
1137     }
1138 }
1139
1140 void
1141 UpdateBuilder::collectInsertions()
1142 {
1143     m_explicitInsertStatements.clear();
1144
1145     // Removing Role properties also removed the contact's PersonContact type,
1146     // so we have to restore it for quick property deletion.
1147     foreach(const QString &classIri, schema().contactClassIris()) {
1148         insertValue(m_contactIri, rdf::type::resource(), ResourceValue(classIri));
1149     }
1150
1151     // Prepare affiliation for work context in case the contact has an organization detail.
1152     // This one should be stored with the same affiliation as the work context.
1153     Value affiliation = lookupAffiliation(QContactDetail::ContextWork);
1154
1155     // Collect inserts for each regular detail.
1156     foreach(const DetailMapping &detail, detailMappings()) {
1157         // Store custom details via nao:Proprty.
1158         if (detail.isCustomDetail()) {
1159             insertCustomDetail(m_contactIri, detail);
1160             continue;
1161         }
1162
1163         const QVariantMap detailValues = detail.fieldValues();
1164         const ResourceTypes resourceTypes = collectResourceTypes(detail);
1165         PredicateValueHash objectCache;
1166
1167         foreach(const QTrackerContactDetailField &field, detail.trackerFields()) {
1168             if (field.hasSubTypes()) {
1169                 // Subtypes are stored indirectly by assigning the proper resource class
1170                 // to the RDF resource representing this field, or by selecting the matching
1171                 // RDF property for storing the detail value.
1172                 continue;
1173             }
1174
1175             if (field.isSynthesized()) {
1176                 // Don't save synthesized fields like SyncTarget
1177                 continue;
1178             }
1179
1180             const QVariantMap::ConstIterator fieldValue = detailValues.find(field.name());
1181
1182             if (fieldValue == detailValues.constEnd()) {
1183                 // Could not find value for this field.
1184                 continue;
1185             }
1186
1187             QVariant rdfValue;
1188
1189             if (not field.makeValue(*fieldValue, rdfValue)) {
1190                 // Could not compute the RDF value for this field.
1191                 continue;
1192             }
1193
1194             if (not detail.hasContext()) {
1195                 insertDetailField(m_contactIri, detail, field, rdfValue, resourceTypes, objectCache);
1196             } else {
1197                 QSet<QString> detailContexts = detail.contexts().toSet();
1198
1199                 if (detailContexts.isEmpty()) {
1200                     detailContexts.insert(QString());
1201                 }
1202
1203                 foreach(const QString &contextName, detailContexts) {
1204                     Value context = lookupAffiliation(contextName);
1205
1206                     insertValue(context, rdf::type::resource(), nco::Affiliation::resource());
1207
1208                     if (not contextName.isEmpty()) {
1209                         insertValue(context, rdfs::label::resource(), LiteralValue(contextName));
1210                     }
1211
1212                     insertValue(m_contactIri, nco::hasAffiliation::resource(), context);
1213                     insertDetailField(context, detail, field, rdfValue, resourceTypes, objectCache);
1214                 }
1215             }
1216         }
1217     }
1218
1219     // Check if the prepared affiliation was used for binding contact details.
1220     // In that case this prepare affiliation must be turned into a proper one.
1221     // we need to insert _:affiliation nco:org _:Organization before
1222     // <contact:42> nco:hasAffiliation _:Organization, so we look for the first
1223     // use of _:affiliation as an object
1224     const QString affiliationSparql = affiliation.sparql();
1225
1226     for (int i = 0; i < m_explicitInsertStatements.size(); ++i) {
1227         const Pattern &statement = m_explicitInsertStatements.at(i);
1228
1229         if (statement.object().sparql() == affiliationSparql) {
1230             insertValue(affiliation, rdfs::label::resource(), LiteralValue(QContactDetail::ContextWork));
1231             insertValue(affiliation, rdf::type::resource(), nco::Affiliation::resource());
1232             break;
1233         }
1234     }
1235
1236     // Insert timestamps and such.
1237     insertValue(m_contactIri, nie::contentLastModified::resource(), LiteralValue(lastChangeTimestamp()));
1238     insertValue(m_contactIri, nie::contentCreated::resource(), LiteralValue(creationTimestamp()),
1239                 preserveTimestamp() ? PreserveOldValue : EnforceNewValue);
1240     insertValue(m_contactIri, nco::contactUID::resource(), LiteralValue(guidDetail().guid()),
1241                 preserveGuid() ? PreserveOldValue : EnforceNewValue);
1242
1243     // Add prepared INSERT statements to the query string.
1244     Insert insert = Insert();
1245     Graph graph = Graph(m_graphIri);
1246     foreach (const Pattern& data, m_explicitInsertStatements) {
1247         graph.addPattern(data);
1248     }
1249     insert.addData(graph);
1250     insert.addRestriction(m_explicitInsertRestrictions);
1251
1252     m_queryString += insert.sparql(m_sparqlOptions);
1253
1254     foreach(const Insert &statement, m_implicitInsertStatements) {
1255         m_queryString += statement.sparql(m_sparqlOptions);
1256     }
1257 }
1258
1259 void
1260 UpdateBuilder::collectRelatedObjects()
1261 {
1262     m_implictlyRelatedObjects.clear();
1263     m_explicitlyRelatedObjects.clear();
1264     m_foreignKeyRelatedObjects.clear();
1265
1266     foreach(const QTrackerContactDetail &detail, schema().details()) {
1267         // skip those not in the detail mask if given
1268         if (isUnknownDetail(detail.name())) {
1269             continue;
1270         }
1271
1272         const QTrackerContactDetailField *const subjectField = detail.resourceIriField();
1273
1274         if (0 != subjectField) {
1275             const PropertyInfoList &subjectPropertyChain = subjectField->propertyChain();
1276             const PropertyInfoBase &subjectProperty = *subjectField->detailUriProperty();
1277
1278             if (subjectPropertyChain.first().isReadOnly()) {
1279                 continue;
1280             }
1281
1282             if (QTrackerContactSubject::isContentScheme(subjectProperty.resourceIriScheme())) {
1283                 // delete related objects by their content IRI
1284                 foreach(const DetailMapping &mapping, detailMappings()) {
1285                     if (mapping.definitionName() != detail.name()) {
1286                         continue;
1287                     }
1288
1289                     const QString &typeIri = subjectProperty.resourceTypeIri();
1290                     const QString &objectIri = mapping.makeResourceIri();
1291
1292                     if (objectIri.isEmpty()) {
1293                         qctWarn(QString::fromLatin1("Empty object IRI for %1 detail which "
1294                                                     "has a content IRI").arg(detail.name()));
1295                         continue;
1296                     }
1297
1298                     m_explicitlyRelatedObjects[detail.name()] +=
1299                             ExplictObjectInfo(typeIri, objectIri, detail);
1300                 }
1301
1302                 continue; // next detail
1303             }
1304
1305             if (subjectField->isForeignKey()) {
1306                 foreach(const DetailMapping &mapping, detailMappings()) {
1307                     const QString &typeIri = subjectProperty.resourceTypeIri();
1308                     const QString &keyIri = subjectField->propertyChain().last().iri();
1309                     const QVariant &value = mapping.fieldValue(subjectField->name());
1310
1311                     if (value.isNull()) {
1312                         continue; // no such field
1313                     }
1314
1315                     m_foreignKeyRelatedObjects[detail.name()] +=
1316                             ForeignObjectInfo(typeIri, keyIri, value, detail);
1317                 }
1318
1319                 continue; // next detail
1320             }
1321         }
1322
1323         // no content IRI known, delete via association
1324         if (not detail.predicateChains().isEmpty()) {
1325             m_implictlyRelatedObjects += detail;
1326         }
1327     }
1328 }
1329
1330 void
1331 UpdateBuilder::appendPredicateChain(const QString &prefix,
1332                                     const PropertyInfoList::ConstIterator &begin,
1333                                     const PropertyInfoList::ConstIterator &end,
1334                                     const QString &target, const QString &suffix)
1335 {
1336     if (begin != end) {
1337         PropertyInfoList::ConstIterator predicate = begin;
1338
1339         m_queryString += prefix;
1340         m_queryString += QLatin1Char('<');
1341         m_queryString += predicate->iri();
1342         m_queryString += QLatin1String("> ");
1343
1344         while(++predicate != end) {
1345             m_queryString += QLatin1String("[ <");
1346             m_queryString += predicate->iri();
1347             m_queryString += QLatin1String("> ");
1348         }
1349
1350         m_queryString += target;
1351
1352         for(int i = (end - begin); i > 1; --i) {
1353             m_queryString += QLatin1String(" ]");
1354         }
1355
1356         m_queryString += suffix;
1357     }
1358 }
1359
1360 void
1361 UpdateBuilder::deleteRelatedObjects()
1362 {
1363     // SPARQL fragments used for building the query.
1364     static const QString implicitQueryPrefix = QLatin1String
1365             ("\n"
1366              "DELETE\n"
1367              "{\n"
1368              "  GRAPH %2\n"
1369              "  {\n"
1370              "    ?subject %1 ?object .\n"
1371              "  }\n"
1372              "}\n"
1373              "WHERE\n"
1374              "{\n"
1375              "  GRAPH %2\n"
1376              "  {\n");
1377     static const QString implicitQuerySuffix = QLatin1String
1378             ("    ?subject %1 ?object .\n"
1379              "  }\n"
1380              "}\n");
1381
1382     static const QString implicitResourcePrefix = QLatin1String("  %1 ");
1383     static const QString implicitResourceSuffix = QLatin1String(" .\n");
1384
1385     static const QString explictObjectPredicateQuery = QLatin1String
1386             ("\n"
1387              "DELETE\n"
1388              "{\n"
1389              "  %1 ?predicate ?object .\n"
1390              "}\n"
1391              "WHERE\n"
1392              "{\n"
1393              "  %1 ?predicate ?object .\n"
1394              "  FILTER(?predicate IN (%2)) .\n"
1395              "}\n");
1396     static const QString explictObjectSubTypeQuery = QLatin1String
1397             ("\n"
1398              "DELETE\n"
1399              "{\n"
1400              "  ?resource a %1 .\n"
1401              "}\n"
1402              "WHERE\n"
1403              "{\n"
1404              "  ?resource a %2 .\n"
1405              "  FILTER(?resource IN (%3)) .\n"
1406              "}\n");
1407     static const QString foreignObjectQuery = QLatin1String
1408             ("\n"
1409              "DELETE\n"
1410              "{\n"
1411              "  ?resource nao:hasProperty ?property .\n"
1412              "}\n"
1413              "WHERE\n"
1414              "{\n"
1415              "  ?resource nao:hasProperty ?property ; %3 ?key .\n"
1416              "  FILTER(?key IN (%4)) .\n"
1417              "}\n"
1418              "\n"
1419              "DELETE\n"
1420              "{\n"
1421              "  ?resource a %1 .\n"
1422              "}\n"
1423              "WHERE\n"
1424              "{\n"
1425              "  ?resource a %2 ; %3 ?key .\n"
1426              "  FILTER(?key IN (%4)) .\n"
1427              "}\n");
1428
1429     // Glue together the SPARQL fragments.
1430
1431 #ifndef QT_NO_DEBUG
1432     m_queryString += QLatin1String
1433             ("\n"
1434              "#----------------------------------------------------.\n"
1435              "# Delete associated objects via their property chain |\n"
1436              "#----------------------------------------------------'\n");
1437 #endif // QT_NO_DEBUG
1438
1439
1440     if (0 != m_contactLocalId) {
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, m_graphIriSparql);
1451                     appendPredicateChain(implicitResourcePrefix.arg(m_contactIriSparql),
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, m_graphIriSparql);
1460                     appendPredicateChain(implicitResourcePrefix.arg(m_contactIriSparql),
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_contactIriSparql, m_graphIriSparql);
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().sparql();
1689
1690     if (not preserveGuid()) {
1691         predicatesOnContact << nco::contactUID::resource().sparql();
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_contactIriSparql, m_graphIriSparql, predicatesList);
1767     }
1768
1769     if (not customDetails.isEmpty()) {
1770         const QString customDetailDefinitionList = QStringList(customDetails.toList()).join(QLatin1String("\",\""));
1771         m_queryString += customDetailsOnContactQuery.arg(m_contactIriSparql, m_graphIriSparql, 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_contactIriSparql, m_graphIriSparql, 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_graphIri);
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(contact)) {
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 (isUnknownDetail(contact, 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 (isUnknownDetail(contact, QContactAvatar::DefinitionName)) {
1969         m_detailMask += QContactAvatar::DefinitionName;
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 that must get resolved into IRIs.
2085     // Group by type since separate resolvers must be used for each contact type.
2086     typedef QList<uint> TrackerIdList;
2087     QHash<QString, TrackerIdList> idsPerContactType;
2088
2089     foreach(const QContact &contact, m_contacts) {
2090         TrackerIdList &idsToLookup = idsPerContactType[contact.type()];
2091         const QContactId &contactId = contact.id();
2092
2093         if (contactId.managerUri() == engine()->managerUri() || contactId.managerUri().isEmpty()) {
2094             idsToLookup += contactId.localId();
2095         } else {
2096             idsToLookup += 0;
2097         }
2098     }
2099
2100     // Resolve contact IRIs for each relevant contact type.
2101     QHash<QContactLocalId, QString> contactIriCache;
2102
2103     for(QHash<QString, TrackerIdList>::ConstIterator
2104         it = idsPerContactType.constBegin(); it != idsPerContactType.constEnd(); ++it) {
2105         QctResourceIriResolver resolver(it.value());
2106         resolver.setClassIris(engine()->schema(it.key()).contactClassIris());
2107
2108         if (not resolver.lookupAndWait()) {
2109             reportError(resolver.errors(), QLatin1String("Cannot resolve local ids of saved contacts"));
2110             return false;
2111         }
2112
2113         // Collect contact IRIs.
2114         // The resolver returns null IRIs when the id was 0, or when no such contact exists.
2115         for(int i = 0, l = resolver.trackerIds().count(); i < l; ++i) {
2116             const QContactLocalId id = resolver.trackerIds().at(i);
2117             const QString &iri = resolver.resourceIris().at(i);
2118
2119             if (not iri.isNull()) {
2120                 contactIriCache.insert(id, iri);
2121             }
2122         }
2123     }
2124
2125     for(int i = 0; i < m_contacts.count(); ++i) {
2126         const QContactLocalId localId = m_contacts.at(i).localId();
2127         const QString &iri = localId ? contactIriCache.value(localId, QString::null)
2128                                      : makeAnonymousIri(QUuid::createUuid());
2129
2130         if (not iri.isNull()) {
2131             m_contactIris += iri;
2132         } else {
2133             m_errorMap.insert(i, QContactManager::DoesNotExistError);
2134             setLastError(QContactManager::DoesNotExistError);
2135             m_contactIris += QString();
2136         }
2137     }
2138
2139     // Check if number of resolved tracker ids matches expectation.
2140     if (m_contacts.count() != m_contactIris.count()) {
2141         reportError("Resolver provides invalid number of tracker ids");
2142         return false;
2143     }
2144
2145     return true;
2146 }
2147
2148 bool
2149 QTrackerContactSaveRequest::resolveContactIds()
2150 {
2151     // We left the saveContact() loop, and don't have any pending updates or
2152     // pending commits. Let's figure out the local ids of the contacts.
2153     QctTrackerIdResolver resolver(m_contactIris);
2154
2155     if (not resolver.lookupAndWait()) {
2156         reportError(resolver.errors(), QLatin1String("Cannot resolve local ids of saved contacts"));
2157         return false;
2158     }
2159
2160     // Assign resolved ids to the individual contacts
2161     const QList<QContactLocalId> &resolvedIds = resolver.trackerIds();
2162
2163     for(int i = 0; i < resolvedIds.count(); ++i) {
2164         if (0 != resolvedIds[i]) {
2165             QContactId id = m_contacts.at(i).id();
2166             id.setManagerUri(engine()->managerUri());
2167             id.setLocalId(resolvedIds[i]);
2168             m_contacts[i].setId(id);
2169         } else if (not m_errorMap.contains(i)) {
2170             qctWarn(QString::fromLatin1("Cannot resolve local id for contact %1/%2 (%3)").
2171                     arg(QString::number(i + 1), QString::number(m_contacts.count()),
2172                         resolver.resourceIris().at(i)));
2173             m_errorMap.insert(i, QContactManager::UnspecifiedError);
2174         }
2175     }
2176
2177     return true;
2178 }
2179
2180 ///////////////////////////////////////////////////////////////////////////////////////////////////
2181
2182 QTrackerAbstractRequest::Dependencies
2183 QTrackerContactSaveRequest::dependencies() const
2184 {
2185     return ResourceCache | GuidAlgorithm;
2186 }
2187
2188 void
2189 QTrackerContactSaveRequest::run()
2190 {
2191     if (not turnIrreversible()) {
2192         return;
2193     }
2194
2195     if (m_contacts.isEmpty()) {
2196         return;
2197     }
2198
2199     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
2200         qDebug()
2201                 << metaObject()->className() << 0
2202                 << ": number of contacts:" << m_contacts.count();
2203     }
2204
2205     m_stopWatch.start();
2206
2207     // figure out resource ids for the passed contacts
2208     if (not resolveContactIris()) {
2209         return;
2210     }
2211
2212     // update tracker with the contacts
2213     QSparqlConnection &connection = QctSparqlConnectionManager::defaultConnection();
2214
2215     if (not connection.isValid()) {
2216         reportError("Cannot save contacts: No valid QtSparql connection.");
2217         return;
2218     }
2219
2220     for(int i = 0; i < m_contacts.count(); ++i) {
2221         QContact &contact = m_contacts[i];
2222         const QContactId contactId = contact.id();
2223         const QString contactManagerUri = contactId.managerUri();
2224
2225         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
2226             qDebug()
2227                     << metaObject()->className() << m_stopWatch.elapsed()
2228                     << ": contact" << i << "- updating";
2229         }
2230
2231         if (m_contactIris.at(i).isEmpty()) {
2232             continue; // skip contacts with non-exisitant, non-zero local id
2233         }
2234
2235         // Conforms to behaviour of Symbian backend, but not clear if this is the right behaviour,
2236         // see QTMOBILITY-1816.
2237         //
2238         // hasselmm: It's also not entirely clear how to deal with some valid local id, but an
2239         // empty manager URI. QTM unit tests don't seem to test this, but relationship tests
2240         // and API suggest that and empty URI shall be interpreted as placeholder for the
2241         // actual manager URI.
2242         if (contactManagerUri != engine()->managerUri() && not contactManagerUri.isEmpty()) {
2243             qctWarn(QString::fromLatin1("Bad id passed for contact %1/%2: manager: \"%3\", id:%4").
2244                     arg(QString::number(i + 1), QString::number(m_contacts.count()),
2245                     contactManagerUri, QString::number(contactId.localId())));
2246             m_errorMap.insert(i, QContactManager::DoesNotExistError);
2247             continue;
2248         }
2249
2250         // reject new contacts on partial save
2251         if (engine()->isRestrictive() && contactId.localId() == 0 && not m_detailMask.isEmpty()) {
2252             qctWarn(QString::fromLatin1("Partial saving not allowed for the new contact %1/%2").
2253                     arg(QString::number(i + 1), QString::number(m_contacts.count())));
2254             m_errorMap.insert(i, QContactManager::BadArgumentError);
2255             continue;
2256         }
2257
2258         // drop odd details, add missing details, collect named details
2259         QHash<QString, QContactDetail> detailsByUri;
2260
2261         {
2262             const QContactManager::Error error = normalizeContact(contact, detailsByUri);
2263
2264             if (error != QContactManager::NoError) {
2265                 qctWarn(QString::fromLatin1("Cannot normalize details for contact %1/%2").
2266                         arg(QString::number(i + 1), QString::number(m_contacts.count())));
2267                 m_errorMap.insert(i, error);
2268                 continue;
2269             }
2270         }
2271
2272         // normalize and save avatar
2273         {
2274             const QContactManager::Error error = writebackThumbnails(contact);
2275
2276             if (error != QContactManager::NoError) {
2277                 qctWarn(QString::fromLatin1("Cannot save avatar thumbnail for contact %1/%2").
2278                         arg(QString::number(i + 1), QString::number(m_contacts.count())));
2279                 m_errorMap.insert(i, error);
2280                 continue;
2281             }
2282         }
2283
2284         // build the update query
2285         UpdateBuilder builder(this, contact, m_contactIris.at(i), detailsByUri, m_detailMask);
2286         const QString queryString = builder.queryString();
2287
2288         // run the update query
2289         const QSparqlQuery query(queryString, QSparqlQuery::InsertStatement);
2290         const QSparqlQueryOptions &options = (m_contacts.count() > 1 ? SyncBatchQueryOptions
2291                                                                      : SyncQueryOptions);
2292         QScopedPointer<QSparqlResult> result(runQuery(query, options, connection));
2293
2294         if (result.isNull()) {
2295             qctWarn(QString::fromLatin1("Save request failed for contact %1/%2").
2296                 arg(QString::number(i + 1), QString::number(m_contacts.count())));
2297             m_errorMap.insert(i, lastError());
2298             continue;
2299         }
2300
2301         if (result->hasError()) {
2302             qctWarn(QString::fromLatin1("Save request failed for contact %1/%2: %3").
2303                 arg(QString::number(i + 1), QString::number(m_contacts.count()),
2304                     qctTruncate(result->lastError().message())));
2305             m_errorMap.insert(i, translateError(result->lastError()));
2306             continue;
2307         }
2308     }
2309
2310     // update contact ids
2311     if (not resolveContactIds()) {
2312         return;
2313     }
2314
2315     // report result
2316     if (lastError() == QContactManager::NoError && not m_errorMap.isEmpty()) {
2317         setLastError(m_errorMap.constBegin().value());
2318     }
2319
2320     QctGarbageCollector::trigger(engine()->gcQueryId(), 1.0*m_contacts.count()/engine()->gcLimit());
2321 }
2322
2323
2324 void
2325 QTrackerContactSaveRequest::updateRequest(QContactManager::Error error)
2326 {
2327     if (engine()->hasDebugFlag(QContactTrackerEngine::ShowTiming)) {
2328         qDebug()
2329                 << metaObject()->className()  << m_stopWatch.elapsed()
2330                 << ": reporting result" << error;
2331     }
2332
2333     engine()->updateContactSaveRequest(staticCast(engine()->request(this).data()),
2334                                        m_contacts, error, m_errorMap,
2335                                        QContactAbstractRequest::FinishedState);
2336 }
2337
2338 ///////////////////////////////////////////////////////////////////////////////////////////////////