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