Changes: Use QString instead of QUrl for Iris
[qtcontacts-tracker:qtcontacts-tracker.git] / src / engine / contactfetchrequest.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 "contactfetchrequest.h"
43
44 #include "engine.h"
45
46 #include <lib/requestextensions.h>
47
48 #include <dao/contactdetail.h>
49 #include <dao/contactdetailschema.h>
50 #include <dao/cubidefines.h>
51 #include <dao/logger.h>
52 #include <dao/resourcecache.h>
53 #include <dao/scalarquerybuilder.h>
54 #include <dao/sparqlconnectionmanager.h>
55 #include <dao/subject.h>
56 #include <dao/support.h>
57
58 #include <QtSparql>
59
60 ///////////////////////////////////////////////////////////////////////////////////////////////////
61
62 CUBI_USE_NAMESPACE
63 CUBI_USE_NAMESPACE_RESOURCES
64
65 ///////////////////////////////////////////////////////////////////////////////////////////////////
66
67 class QTrackerContactFetchRequest::DetailContext
68 {
69 public:
70     explicit DetailContext(const QTrackerContactDetail &definition,
71                            int firstColumn, int lastColumn)
72         : m_definition(definition)
73         , m_firstColumn(firstColumn)
74         , m_lastColumn(lastColumn)
75     {
76     }
77
78     const QTrackerContactDetail & definition() const { return m_definition; }
79     int firstColumn() const { return m_firstColumn; }
80     int lastColumn() const { return m_lastColumn; }
81
82 private:
83     QTrackerContactDetail   m_definition;
84     int                     m_firstColumn;
85     int                     m_lastColumn;
86 };
87
88 ///////////////////////////////////////////////////////////////////////////////////////////////////
89
90 class QTrackerContactFetchRequest::QueryContext
91 {
92 public:
93     explicit QueryContext(const QTrackerContactDetailSchema &schema)
94         : customDetailColumn(-1)
95         , fetchAllDetails(false)
96         , m_schema(schema)
97     {
98     }
99
100 public: // attributes
101     const QTrackerContactDetailSchema & schema() const { return m_schema; }
102     const QString & contactType() const { return m_schema.contactType(); }
103
104 public: // fields
105     Select query;
106     QSparqlResult *result;
107
108     QList<DetailContext> details;
109     QSet<QString> customDetailHints;
110     int customDetailColumn;
111
112     bool fetchAllDetails : 1;
113
114 private: // fields
115     QTrackerContactDetailSchema m_schema;
116 };
117
118 ///////////////////////////////////////////////////////////////////////////////////////////////////
119
120 QTrackerContactFetchRequest::QTrackerContactFetchRequest(QContactAbstractRequest *request,
121                                                          QContactTrackerEngine *engine,
122                                                          QObject *parent)
123     : QTrackerBaseRequest<QContactFetchRequest>(request, engine, parent)
124 {
125 }
126
127 QTrackerContactFetchRequest::~QTrackerContactFetchRequest()
128 {
129 }
130
131 Select
132 QTrackerContactFetchRequest::baseQuery(const QTrackerScalarContactQueryBuilder &queryBuilder)
133 {
134     Variable contact(queryBuilder.contact());
135     Variable context(queryBuilder.context());
136
137     Select query;
138
139     query.addProjection(contact);
140     query.addProjection(Functions::trackerId.apply(contact));
141     query.addProjection(context);
142     query.addProjection(rdfs::label::function().apply(context));
143
144     foreach(const QString &classIri, queryBuilder.schema().contactClassIris()) {
145         query.addRestriction(contact, rdf::type::resource(), ResourceValue(classIri));
146     }
147
148     PatternGroup contextPattern;
149     contextPattern.setOptional(true);
150     contextPattern.addPattern(contact, nco::hasAffiliation::resource(), context);
151     query.addRestriction(contextPattern);
152
153     return query;
154 }
155
156 static bool
157 bindFilters(QTrackerScalarContactQueryBuilder &queryBuilder,
158             const QContactFilter &filter, Select &select)
159 {
160     Filter result;
161
162     if (queryBuilder.bindFilter(filter, result)) {
163         select.setFilter(result);
164         return true;
165     }
166
167     return false;
168 }
169
170 QContactManager::Error
171 QTrackerContactFetchRequest::bindDetails(QueryContext &context)
172 {
173     QTrackerScalarContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
174
175     context.query = baseQuery(queryBuilder);
176
177     foreach(const QTrackerContactDetail &detail, context.schema().details()) {
178         if (detail.isSynthesized()) {
179             continue;
180         }
181
182         // skip details which are not needed according to the fetch hints
183         if (not m_definitionHints.contains(detail.name()) && not m_definitionHints.isEmpty()) {
184             continue;
185         }
186
187         if (detail.fields().isEmpty()) {
188             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
189                 qctWarn(QString::fromLatin1("Not implemented yet, skipping %1 detail").
190                         arg(detail.name()));
191             }
192
193             continue;
194         }
195
196         // bind this detail to the proper query
197         const int firstColumn = context.query.projections().count();
198         queryBuilder.bindFields(detail, context.query);
199
200         // verify results
201         if (QContactManager::NoError != queryBuilder.error()) {
202             context.query = Select();
203             return queryBuilder.error();
204         }
205
206         if (context.query == Select()) {
207             qctWarn("No queries built");
208             return QContactManager::UnspecifiedError;
209         }
210
211         // store this detail's context
212         const int lastColumn = context.query.projections().count();
213         context.details += DetailContext(detail, firstColumn, lastColumn);
214     }
215
216     // bind the filters
217     if (not bindFilters(queryBuilder, m_request->filter(), context.query)) {
218         return queryBuilder.error();
219     }
220
221     // Create almost empty base query if actually no unique details where requested by the
222     // fetch hints. This is needed to let fetchBaseModel() populate the contact cache, and more
223     // importantly it is needed to avoid bogus "DoesNotExistError" errors if existing contacts
224     // are requested via local id filter, but those contacts don't have any of the requested
225     // details.
226     if (context.query == Select()) {
227         QTrackerScalarContactQueryBuilder queryBuilder(context.schema(), engine()->managerUri());
228
229         context.query = baseQuery(queryBuilder);
230         bindFilters(queryBuilder, m_request->filter(), context.query);
231
232         if (QContactManager::NoError != queryBuilder.error()) {
233             context.query = Select();
234             return queryBuilder.error();
235         }
236     }
237
238     return QContactManager::NoError;
239 }
240
241 QContactManager::Error
242 QTrackerContactFetchRequest::buildQueries()
243 {
244     const QTrackerContactDetailSchemaMap &supportedSchemas = engine()->schemas();
245
246     m_definitionHints = m_request->fetchHint().detailDefinitionsHint().toSet();
247     m_nameOrder = qctRequestGetNameOrder(m_request);
248     m_queryContexts.clear();
249
250     for(QTrackerContactDetailSchemaMap::ConstIterator schema =
251         supportedSchemas.begin(); schema != supportedSchemas.end(); ++schema) {
252         m_queryContexts.append(QueryContext(*schema));
253
254         if (not m_definitionHints.isEmpty()) {
255             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
256                 qDebug() << "explicit definition hints:" << m_definitionHints;
257             }
258
259             // Make sure the display name can be synthesized when needed
260             if (m_definitionHints.contains(QContactDisplayLabel::DefinitionName)) {
261                 engine()->ensureDisplayLabelDetails(m_definitionHints, m_nameOrder);
262             }
263
264             // Make sure all we can synthesized requested details
265             foreach(const QString &name, m_definitionHints) {
266                 const QTrackerContactDetail *const detail = schema->detail(name);
267
268                 if (0 != detail) {
269                     m_definitionHints.unite(detail->dependencies());
270                 } else if (not schema->isSyntheticDetail(name)) {
271                     m_queryContexts.last().customDetailHints += name;
272                 }
273             }
274
275             if (engine()->hasDebugFlag(QContactTrackerEngine::ShowNotes)) {
276                 qDebug() << "final definition hints:" << m_definitionHints;
277                 qDebug() << "custom detail hints:" << m_queryContexts.last().customDetailHints;
278             }
279         } else {
280             m_queryContexts.last().fetchAllDetails = true;
281         }
282     }
283
284     for(QList<QueryContext>::Iterator context = m_queryContexts.begin();
285         context != m_queryContexts.end(); ++context) {
286         const QContactManager::Error error = bindDetails(*context);
287
288         if (QContactManager::NoError != error) {
289             return error;
290         }
291
292         // Build custom detail query when needed
293         if (not context->customDetailHints.isEmpty() || context->fetchAllDetails) {
294             QTrackerScalarContactQueryBuilder queryBuilder(context->schema(), engine()->managerUri());
295             context->customDetailColumn = context->query.projections().count();
296             queryBuilder.bindCustomDetails(context->query, context->customDetailHints);
297         }
298
299         if (context->query == Select()) {
300             qctWarn("No queries built");
301             return QContactManager::UnspecifiedError;
302         }
303     }
304
305     return QContactManager::NoError;
306 }
307
308 const Select &
309 QTrackerContactFetchRequest::query(const QString &contactType,
310                                    QContactManager::Error &error)
311 {
312     if (m_queryContexts.isEmpty()) {
313         error = buildQueries();
314     } else {
315         error = QContactManager::NoError;
316     }
317
318     foreach(const QueryContext &context, m_queryContexts) {
319         if (context.contactType() == contactType) {
320             return context.query;
321         }
322     }
323
324     static const Select nullSelect = Select();
325     error = QContactManager::BadArgumentError;
326
327     return nullSelect;
328 }
329
330 bool
331 QTrackerContactFetchRequest::start()
332 {
333     // build RDF queries
334     const QContactManager::Error error = buildQueries();
335
336     if (QContactManager::NoError != error) {
337         setLastError(error);
338         QMetaObject::invokeMethod(this, "emitResult", Qt::QueuedConnection);
339         return true;
340     }
341
342     // launch the RDF queries
343     engine()->updateRequestState(m_request, QContactAbstractRequest::ActiveState);
344
345     for(QList<QueryContext>::Iterator context =
346         m_queryContexts.begin(); context != m_queryContexts.end(); ++context) {
347         if (engine()->hasDebugFlag(QContactTrackerEngine::ShowSelects)) {
348             qDebug() << context->query.sparql(Options::DefaultSparqlOptions |
349                                               Options::PrettyPrint);
350         }
351
352         const QSparqlQuery query(context->query.sparql());
353         QScopedPointer<QSparqlResult> result(runQuery(query));
354
355         if (result.isNull()) {
356             return false;
357         }
358
359         context->result = result.data();
360         m_pendingQueries.append(result.take());
361     }
362
363     return true;
364 }
365
366 /// Returns a list of integers created from the strings in @p stringList.
367 /// The integers are in the order of the string, if a string could not be converted,
368 /// the corresponding integer is @c 0.
369 static QList<int>
370 toIntList(const QStringList &stringList)
371 {
372     QList<int> intList;
373
374     foreach(const QString &element, stringList) {
375         intList.append(element.toInt());
376     }
377
378     return intList;
379 }
380
381 /// Returns a list of integers created from the content of @p string.
382 /// The string is separated using @p separator (defaults to QTrackerScalarContactQueryBuilder::listSeparator()),
383 /// the integers are in the order of the separated substrings.
384 /// If a substring could not be converted, the corresponding integer is @c 0.
385 static QList<int>
386 toIntList(const QString &string,
387           const QChar separator = QTrackerScalarContactQueryBuilder::listSeparator())
388 {
389     return toIntList(string.split(separator));
390 }
391
392 /// returns the subtype(s) for the given field as a QVariant.
393 /// Uses the default subtype(s) if the passed @p subTypes is empty.
394 static QVariant
395 fetchSubTypes(const QTrackerContactDetailField &field,
396               QSet<QString> subTypes)
397 {
398     // apply default value if no subtypes could be read
399     if (subTypes.isEmpty() && field.hasDefaultValue()) {
400         switch(field.defaultValue().type()) {
401         case QVariant::String:
402             subTypes.insert(field.defaultValue().toString());
403             break;
404         case QVariant::StringList:
405             subTypes = field.defaultValue().toStringList().toSet();
406             break;
407         default:
408             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %2").
409                     arg(QLatin1String(field.defaultValue().typeName()), field.name()));
410             break;
411         }
412     }
413
414     // set field if any subtypes could be identified
415     if (not subTypes.isEmpty()) {
416         switch(field.dataType()) {
417         case QVariant::String:
418             // TODO: clone this detail multiple times when |subTypes| > 1
419             return *subTypes.begin();
420             break;
421
422         case QVariant::StringList:
423             return QStringList(subTypes.toList());
424             break;
425
426         default:
427             qctWarn(QString::fromLatin1("Invalid type %1 for subtype field %2").
428                     arg(QLatin1String(QVariant::typeToName(field.dataType())), field.name()));
429             break;
430         }
431     }
432
433     return QVariant();
434 }
435
436 static int
437 trackerId(const ResourceInfo &resource)
438 {
439     return QctResourceCache::instance().trackerId(resource.iri());
440 }
441
442 /// Returns the subtypes encoded in @p rawValueString as string or stringlist in a QVariant.
443 /// Returns the default subtype(s) if there is no known subtype in @p rawValueString.
444 static QVariant
445 fetchSubTypesClasses(const QTrackerContactDetailField &field,
446                      const QString &rawValueString)
447 {
448     const QList<int> fetchedSubTypes = toIntList(rawValueString);
449     QSet<QString> subTypes;
450
451     foreach(const ClassInfoBase &ci, field.subTypeClasses()) {
452         if (fetchedSubTypes.contains(trackerId(ci))) {
453             subTypes.insert(ci.text());
454         }
455     }
456
457     return fetchSubTypes(field, subTypes);
458 }
459
460 /// Returns the list of details, with each detail having all subtypes collected and set.
461 /// If there was no subtype for a detail, it was set the default subtype(s).
462 static QList<QContactDetail>
463 unifyPropertySubTypes(const QMultiHash<QString, QContactDetail> &details,
464                       const QTrackerContactDetailField &subTypeField)
465 {
466     QList<QContactDetail> results;
467     QStringList subTypes = details.uniqueKeys();
468     subTypes.removeOne(QString());
469
470     foreach(QContactDetail detail, details.values(QString())) {
471         QSet<QString> detailSubTypes;
472
473         foreach(const QString &subType, subTypes) {
474             if(details.values(subType).contains(detail)) {
475                 detailSubTypes.insert(subType);
476             }
477         }
478
479         detail.setValue(subTypeField.name(), fetchSubTypes(subTypeField, detailSubTypes));
480
481         results.append(detail);
482     }
483
484     return results;
485 }
486
487 QVariant
488 QTrackerContactFetchRequest::fetchInstances(const QTrackerContactDetailField &field,
489                                             const QString &rawValueString) const
490 {
491     if (rawValueString.isEmpty()) {
492         return QVariant();
493     }
494
495     // apply string list when requested
496     if (QVariant::StringList == field.dataType()) {
497         const QList<int> fetchedInstances = toIntList(rawValueString);
498         QStringList instances;
499
500         foreach(const InstanceInfoBase &ii, field.allowableInstances()) {
501             if (fetchedInstances.contains(trackerId(ii))) {
502                 instances.append(ii.text());
503             }
504         }
505
506         // append default value if instances could not be identified
507         if (instances.isEmpty() && field.hasDefaultValue()) {
508             instances.append(field.defaultValue().toString());
509         }
510
511         return instances;
512     }
513
514     // otherwise apply single value
515     bool hasInstanceId = false;
516     const int instanceId = rawValueString.toInt(&hasInstanceId);
517
518     if (hasInstanceId) {
519         foreach(const InstanceInfoBase &instance, field.allowableInstances()) {
520             if (trackerId(instance) == instanceId) {
521                 return instance.value();
522             }
523         }
524     }
525
526     qctWarn(QString::fromLatin1("Unknown instance id for field %1").
527             arg(field.name()));
528
529     // apply default value if instances could not be identified
530     if (field.hasDefaultValue()) {
531         return field.defaultValue();
532     } else {
533         return QVariant();
534     }
535 }
536
537 /// Returns @c true if @p variant is an empty string or an empty stringlist.
538 static bool
539 isEmptyStringOrStringList(const QVariant &variant)
540 {
541     return((QVariant::String == variant.type() && variant.toString().isEmpty()) ||
542            (QVariant::StringList == variant.type() && variant.toStringList().isEmpty()));
543 }
544
545 QVariant
546 QTrackerContactFetchRequest::fetchField(const QTrackerContactDetailField &field,
547                                         const QVariant &rawValue) const
548 {
549     QString rawValueString = rawValue.toString();
550
551     if (field.hasSubTypeClasses()) {
552         return fetchSubTypesClasses(field, rawValueString);
553     }
554
555     if (rawValueString.isEmpty()) {
556         return QVariant();
557     }
558
559     if (field.hasSubTypeProperties()) {
560         return rawValue;
561     }
562
563     if (field.restrictsValues()) {
564         if (not field.allowableInstances().isEmpty()) {
565             return fetchInstances(field, rawValueString);
566         }
567
568         if (not field.allowableValues().isEmpty()) {
569             if (QVariant::StringList == field.dataType()) {
570                 return rawValue.toString().split(QTrackerScalarContactQueryBuilder::listSeparator());
571             } else {
572                 return rawValue.toString();
573             }
574         }
575     }
576
577     QVariant parsedValue;
578
579     if (not field.parseValue(rawValue, parsedValue)) {
580         qctWarn(QString::fromLatin1("Cannot convert value to %1 for field %2: %3").
581                 arg(QLatin1String(QVariant::typeToName(field.dataType())),
582                     field.name(), rawValue.toString()));
583
584         return QVariant();
585     }
586
587     if (isEmptyStringOrStringList(parsedValue)) {
588         return QVariant();
589     }
590
591     return parsedValue;
592 }
593
594 void
595 QTrackerContactFetchRequest::fetchCustomValues(const QTrackerContactDetailField &field,
596                                                QVariant &fieldValue,
597                                                const QVariant &rawValue) const
598 {
599     switch (field.dataType()) {
600     case QVariant::StringList: {
601         QStringList values =
602                 fieldValue.toStringList() +
603                 rawValue.toString().split(QTrackerScalarContactQueryBuilder::listSeparator());
604
605         values.removeAll(QString());
606         values.removeDuplicates();
607
608         if (not values.isEmpty()) {
609             fieldValue = values;
610         } else {
611             fieldValue = QVariant();
612         }
613
614         break;
615     }
616
617     case QVariant::String: {
618         const QString value = rawValue.toString();
619
620         if (not value.isEmpty()) {
621             fieldValue = value;
622         }
623
624         break;
625     }
626
627     default:
628         qctWarn(QString::fromLatin1("Cannot fetch custom values for field %2: "
629                                     "Data type %3 is not supported yet.").
630                 arg(field.name(), QLatin1String(QVariant::typeToName(field.dataType()))));
631         break;
632     }
633 }
634
635 bool
636 QTrackerContactFetchRequest::saveDetail(ContactCache::iterator contact,
637                                         QContactDetail &detail,
638                                         const QTrackerContactDetail &definition,
639                                         const QString &contactIri)
640 {
641     if (not detail.detailUri().isEmpty()) {
642         QString detailUri;
643
644         // Detail URI scheme is different from resource IRI scheme
645         // for details like QContactPresence or online avatars.
646         if (definition.detailUriScheme() != definition.resourceIriScheme()) {
647             bool valid = false;
648             QVariant value;
649
650             value = QTrackerContactSubject::parseIri(definition.resourceIriScheme(),
651                                                      detail.detailUri(), &valid);
652
653             if (valid) {
654                 detailUri = QTrackerContactSubject::makeIri(definition.detailUriScheme(),
655                                                             QVariantList() << value);
656             }
657         }
658
659         if (not detailUri.isEmpty()) {
660             detail.setLinkedDetailUris(detail.detailUri());
661             detail.setDetailUri(detailUri);
662         } else {
663             detail.setDetailUri(detail.detailUri());
664         }
665     } else if (definition.hasContext()) {
666         QString context(detail.contexts().first());
667         detail.setDetailUri(contactIri + QLatin1Char('#') +
668                             definition.name() + QLatin1Char('-') +
669                             context);
670     } else {
671         detail.setDetailUri(contactIri + QLatin1Char('#') + definition.name());
672     }
673
674     return contact->saveDetail(&detail);
675 }
676
677 void
678 QTrackerContactFetchRequest::fetchUniqueDetail(QList<QContactDetail> &details,
679                                                const DetailContext &context,
680                                                const QString &affiliation,
681                                                const QSparqlResultRow &row)
682 {
683     QContactDetail detail(context.definition().name());
684
685     int lastColumn = context.firstColumn();
686
687     // FIXME: we don't support subtypes by class for unique details
688
689     bool empty = true;
690     foreach(const QTrackerContactDetailField &field, context.definition().fields()) {
691         if (not field.hasPropertyChain()) {
692             continue;
693         }
694
695         const QVariant rawValue = row.value(lastColumn++);
696
697         if (rawValue.toString().isEmpty()) {
698             continue;
699         }
700
701         const QVariant fieldValue = fetchField(field, rawValue);
702
703         if (fieldValue.isNull()) {
704             continue;
705         }
706
707         if (not field.hasSubTypes()) {
708             detail.setValue(field.name(), fieldValue);
709             empty = false;
710         } else {
711             if (field.hasSubTypeProperties()) {
712                 // collect subtypes by those property subtype columns which have some string set
713                 QSet<QString> subTypes;
714
715                 foreach(const PropertyInfoBase &pi, field.subTypeProperties()) {
716                     if (lastColumn == row.count()) {
717                         qctWarn(QString::fromLatin1("Trying to fetch more detail fields than we have "
718                                                     "columns for field %1 subtypes").
719                                 arg(field.name()));
720                         return;
721                     }
722
723                     if (not row.value(lastColumn++).toString().isEmpty()) {
724                         subTypes.insert(pi.value().toString());
725                     }
726                 }
727
728                 fetchSubTypes(field, subTypes);
729             }
730         }
731     }
732
733     // if detail is empty, return as it is ignored - no need to update uri
734     if (empty) {
735         return;
736     }
737
738     if (context.definition().hasDetailUri()) {
739         // If the detailUri was on nco:hasAffiliation, we don't have anything to
740         // do since we already know the IRI of the affiliation. If it was on
741         // another property, it is always stored in the first column
742         const QTrackerContactDetailField *detailUriField = context.definition().detailUriField();
743
744         foreach (const PropertyInfoBase &pi, detailUriField->propertyChain()) {
745             if (pi.hasDetailUri()) {
746                 if (pi.iri() == nco::hasAffiliation::iri()) {
747                     detail.setDetailUri(affiliation);
748                 } else {
749                     detail.setDetailUri(row.value(lastColumn++).toString());
750                 }
751                 break;
752             }
753         }
754     }
755
756     details.append(detail);
757 }
758
759 static void
760 fetchMultiDetailUri(QContactDetail &detail,
761                     const QTrackerContactDetail& definition,
762                     QStringList &fieldsData)
763 {
764     if (definition.hasDetailUri()) {
765         // If the detailUri was on nco:hasAffiliation, we don't have anything to
766         // do since we already know the IRI of the affiliation. If it was on
767         // another property, it is always stored in the first column
768         const QTrackerContactDetailField *detailUriField = definition.detailUriField();
769
770         PropertyInfoList::ConstIterator pi = detailUriField->propertyChain().constBegin();
771
772         for(; pi != detailUriField->propertyChain().constEnd(); ++pi) {
773             if (pi->hasDetailUri()) {
774                 detail.setDetailUri(fieldsData.takeFirst());
775                 break;
776             }
777         }
778     }
779 }
780
781 /// creates a copy of @p _fields with all fields with an inverted property in the chain at the end
782 static QList<QTrackerContactDetailField>
783 moveWithInversePropertyAtEnd(const QList<QTrackerContactDetailField>& fields)
784 {
785     QList<QTrackerContactDetailField> sortedFields;
786     QList<QTrackerContactDetailField> inverseFields;
787
788     foreach(const QTrackerContactDetailField &field, fields) {
789         if (not field.hasPropertyChain()) {
790             continue;
791         }
792
793         if (field.propertyChain().hasInverseProperty()) {
794             inverseFields.append(field);
795         } else {
796             sortedFields.append(field);
797         }
798     }
799
800     sortedFields += inverseFields;
801     return sortedFields;
802 }
803
804 void
805 QTrackerContactFetchRequest::fetchMultiDetail(QContactDetail &detail,
806                                               const DetailContext &context,
807                                               const QString &rawValueString)
808 {
809     QStringList fieldsData = rawValueString.split(QTrackerScalarContactQueryBuilder::fieldSeparator());
810
811     fetchMultiDetailUri(detail, context.definition(), fieldsData);
812
813     // Fields that have inverse properties are always stored last, so respect
814     // this order when fetching them
815     const QList<QTrackerContactDetailField> fields =
816         moveWithInversePropertyAtEnd(context.definition().fields());
817
818     foreach(const QTrackerContactDetailField &field, fields) {
819         // Property subtypes are done in unifyPropertySubTypes
820         if (field.hasSubTypeProperties()) {
821             continue;
822         }
823
824         if (fieldsData.empty()) {
825             qctWarn(QString::fromLatin1("Trying to fetch more detail fields than we have "
826                                         "columns for detail %1").arg(context.definition().name()));
827             return;
828         }
829
830         // FIXME: QVariant -> QString -> QVariant ?
831         QVariant fieldValue;
832
833         if (not field.isWithoutMapping()) {
834             fieldValue = fetchField(field, qVariantFromValue(fieldsData.takeFirst()));
835         }
836
837         if (field.permitsCustomValues()) {
838             if (fieldsData.empty()) {
839                 qctWarn(QString::fromLatin1("Missing custom values for field %1 of detail %2").
840                                             arg(field.name(), context.definition().name()));
841             }
842
843             fetchCustomValues(field, fieldValue, fieldsData.takeFirst());
844         }
845
846         if (not fieldValue.isNull()) {
847             detail.setValue(field.name(), fieldValue);
848         }
849     }
850
851 }
852
853 /* Enhances \sa QContactDetail::isEmpty by ignoring context and detail uri - stored also as values */
854 static bool
855 areContactDetailDataValuesEmpty(const QContactDetail &detail)
856 {
857     const QVariantMap variantValues = detail.variantValues();
858     if (variantValues.empty()) {
859         return true;
860     }
861     if (variantValues.size() <= 2) {
862         QStringList keys(variantValues.keys());
863         keys.removeOne(QContactDetail::FieldDetailUri);
864         keys.removeOne(QContactDetail::FieldLinkedDetailUris);
865         return (keys.size() == 0);
866     }
867     return false;
868 }
869
870 void
871 QTrackerContactFetchRequest::fetchMultiDetails(QList<QContactDetail> &details,
872                                                const DetailContext &detailContext,
873                                                const QSparqlResultRow &row)
874 {
875     int lastColumn = detailContext.firstColumn();
876
877     const QTrackerContactDetailField *subTypeField = detailContext.definition().subTypeField();
878
879     // First demarshall the raw data
880     const QString rawValueString = row.value(lastColumn++).toString();
881
882     if (rawValueString.isEmpty()) {
883         return;
884     }
885
886     QHash<QString, QString> subTypesDetailsData;
887     subTypesDetailsData.insert(QString(), rawValueString);
888
889     // Subtype properties are spread over various details (one for
890     // each subtype)
891     // If we have subtype properties, fetch as many details as we
892     // have subtypes
893     if (subTypeField && subTypeField->hasSubTypeProperties()) {
894         // Only used for debug message
895         const QString definitionName(detailContext.definition().name());
896
897         foreach(const PropertyInfoBase &pi, subTypeField->subTypeProperties()) {
898             if (lastColumn > detailContext.lastColumn()) {
899                 qctWarn(QString::fromLatin1("Trying to fetch more subtype details "
900                                             "than we have columns for detail %1").
901                         arg(definitionName));
902                 return;
903             }
904
905             const QString rawSubTypeData = row.value(lastColumn++).toString();
906
907             if (rawSubTypeData.isEmpty()) {
908                 continue;
909             }
910
911             subTypesDetailsData.insert(pi.value().toString(), rawSubTypeData);
912         }
913     }
914
915     // Now parse each fetched detail into a proper QContactDetail,
916     // keeping the info about its subtype
917     QMultiHash<QString, QContactDetail> subTypeDetails;
918     foreach(const QString &subType, subTypesDetailsData.keys()) {
919         const QString rawDetailsData = subTypesDetailsData.value(subType);
920         const QStringList detailsData = rawDetailsData.split(QTrackerScalarContactQueryBuilder::detailSeparator());
921
922         foreach(const QString &detailData, detailsData) {
923             QContactDetail detail(detailContext.definition().name());
924
925             if (detailData.isEmpty()) {
926                 continue;
927             }
928
929             fetchMultiDetail(detail, detailContext, detailData);
930             subTypeDetails.insert(subType,detail);
931         }
932     }
933
934     // And finally unify the fetched details
935
936     // There is at least one key, the one of the default type
937     if (subTypeField && subTypeField->hasSubTypeProperties()) {
938         details.append(unifyPropertySubTypes(subTypeDetails, *subTypeField));
939     } else {
940         details.append(subTypeDetails.values());
941     }
942 }
943
944 void
945 QTrackerContactFetchRequest::fetchResults(ContactCache &results, const QueryContext &queryContext)
946 {
947     QSparqlResult *const result = queryContext.result;
948
949     for(bool hasRow = result->first(); hasRow; hasRow = result->next()) {
950         const QSparqlResultRow & row = result->current();
951
952         // identify the contact
953         const QString contactIri = row.value(0).toString();
954         const QContactLocalId localId = row.value(1).toUInt();
955         const QString affiliation = row.value(2).toString();
956         const QString affiliationContext = qctCamelCase(row.value(3).toString());
957
958         // create contact if not already existing
959         ContactCache::Iterator contact(results.find(localId));
960
961         if(contact == results.end()) {
962             QContact c;
963             QContactId id;
964             id.setLocalId(localId);
965             id.setManagerUri(engine()->managerUri());
966             c.setId(id);
967             c.setType(queryContext.contactType());
968
969             contact = results.insert(localId, c);
970         }
971
972         // read details
973         for(QList<DetailContext>::ConstIterator detailContext = queryContext.details.constBegin();
974             detailContext != queryContext.details.constEnd();
975             ++detailContext) {
976             QList<QContactDetail> details;
977
978             if (detailContext->definition().isUnique()) {
979                 fetchUniqueDetail(details, *detailContext, affiliation, row);
980             } else {
981                 fetchMultiDetails(details, *detailContext, row);
982             }
983
984             foreach(QContactDetail detail, details) {
985                 // Ignore the detail if we fetched no fields for it (or just detailUri)
986                 if (areContactDetailDataValuesEmpty(detail)) {
987                     continue;
988                 }
989
990                 if (detailContext->definition().hasContext()) {
991                     if (affiliationContext.isEmpty()) {
992                         detail.setContexts(QContactDetail::ContextOther);
993                     } else {
994                         detail.setContexts(affiliationContext);
995                     }
996                 }
997
998                 if (not saveDetail(contact, detail, detailContext->definition(), contactIri)) {
999                     qctWarn(QString::fromLatin1("Could not save detail %1 on contact %2").
1000                             arg (detail.definitionName(), contactIri));
1001                 }
1002             }
1003         }
1004
1005         fetchCustomDetails(queryContext, contact, row);
1006     }
1007 }
1008
1009 QContactDetail
1010 QTrackerContactFetchRequest::fetchCustomDetail(const QString &rawValue, const QString &contactType)
1011 {
1012     QStringList tokens = rawValue.split(QTrackerScalarContactQueryBuilder::fieldSeparator());
1013
1014     // Minimum number of tokens is detail name + 1 field name/value tuple
1015     if (tokens.size() < 3) {
1016         return QContactDetail();
1017     }
1018
1019     QMultiHash<QString, QString> detailValues;
1020     QString detailName = tokens.takeFirst();
1021
1022     QContactDetail detail(detailName);
1023
1024     const QContactDetailDefinitionMap detailDefs = engine()->detailDefinitions(contactType, 0);
1025
1026
1027     while(tokens.size() >= 2) {
1028         QString fieldName = tokens.takeFirst();
1029         QStringList fieldValues = tokens.takeFirst().split(QTrackerScalarContactQueryBuilder::listSeparator());
1030         foreach(const QString &f, fieldValues) {
1031             detailValues.insert(fieldName, f);
1032         }
1033     }
1034
1035     foreach(const QString &fieldName, detailValues.uniqueKeys()) {
1036         QMap<uint, QString> orderedValues;
1037
1038         // Values are retrieved as "tracker-id:value" pairs.
1039         // Order values by tracker-id and extract the value.
1040         foreach(const QString &s, detailValues.values(fieldName)) {
1041             const int i = s.indexOf(QLatin1Char(':'));
1042             orderedValues.insert(s.left(i).toUInt(), s.mid(i + 1));
1043         }
1044
1045         // QVariant cannot deal with QList<QString> :-/
1046         const QStringList fieldValues = orderedValues.values();
1047         QVariant fieldValue;
1048
1049         if (fieldValues.size() == 1) {
1050             fieldValue = fieldValues.first();
1051         } else {
1052             fieldValue = fieldValues;
1053         }
1054
1055         detail.setValue(fieldName, fieldValue);
1056
1057         const QContactDetailFieldDefinitionMap fieldDefs = detailDefs[detail.definitionName()].fields();
1058         const QContactDetailFieldDefinition fieldDef = fieldDefs.value(fieldName);
1059
1060         if (QVariant::Invalid != fieldDef.dataType() && fieldValue.convert(fieldDef.dataType())) {
1061             detail.setValue(fieldName, fieldValue);
1062         }
1063     }
1064
1065     return detail;
1066 }
1067
1068 void
1069 QTrackerContactFetchRequest::fetchCustomDetails(const QueryContext &queryContext,
1070                                                 ContactCache::iterator contact,
1071                                                 const QSparqlResultRow &row)
1072 {
1073     if (queryContext.customDetailColumn < 0 ||
1074         queryContext.customDetailColumn >= row.count()) {
1075         return;
1076     }
1077
1078     const QString rawValue = row.value(queryContext.customDetailColumn).toString();
1079
1080     foreach(const QString &rawDetailValue, rawValue.split(QTrackerScalarContactQueryBuilder::detailSeparator())) {
1081         QContactDetail detail = fetchCustomDetail(rawDetailValue, contact->type());
1082
1083         if (not areContactDetailDataValuesEmpty(detail)) {
1084             contact->saveDetail(&detail);
1085         }
1086     }
1087 }
1088
1089 static void
1090 updateDetailLinks(QContact &contact)
1091 {
1092     typedef QHash<QString, QContactDetail> DetailHash;
1093
1094     DetailHash detailHash;
1095
1096     foreach(const QContactDetail &detail, contact.details()) {
1097         if (not detail.detailUri().isEmpty()) {
1098             detailHash.insert(detail.detailUri(), detail);
1099         }
1100     }
1101
1102     for(DetailHash::Iterator detail = detailHash.begin(); detail != detailHash.end(); ++detail) {
1103         foreach(const QString &detailUri, detail->linkedDetailUris()) {
1104             DetailHash::Iterator target = detailHash.find(detailUri);
1105
1106             if (target == detailHash.end()) {
1107                 continue;
1108             }
1109
1110             QStringList linkedDetailUris = target->linkedDetailUris();
1111
1112             if (linkedDetailUris.contains(detail->detailUri())) {
1113                 continue;
1114             }
1115
1116             linkedDetailUris.append(detail->detailUri());
1117             target->setLinkedDetailUris(linkedDetailUris);
1118             contact.saveDetail(&target.value());
1119         }
1120     }
1121 }
1122
1123 static void
1124 removeDuplicateDetails(QContact &contact)
1125 {
1126     QSet<QContactDetail> knownDetails;
1127
1128     foreach(QContactDetail detail, contact.details()) {
1129         if(knownDetails.contains(detail)) {
1130             contact.removeDetail(&detail);
1131         } else {
1132             knownDetails.insert(detail);
1133         }
1134     }
1135 }
1136
1137 bool
1138 QTrackerContactFetchRequest::handleQueryError(QSparqlResult */*result*/, QContactManager::Error /*error*/)
1139 {
1140     // clean up pending queries
1141     while(not m_pendingQueries.isEmpty()) {
1142         QSparqlResult *result = m_pendingQueries.takeFirst();
1143         result->disconnect(this);
1144     }
1145
1146     return true;
1147 }
1148
1149 bool
1150 QTrackerContactFetchRequest::handleQuerySuccess(QSparqlResult *result)
1151 {
1152     m_pendingQueries.removeOne(result);
1153
1154     if (not m_pendingQueries.isEmpty()) {
1155         return false;
1156     }
1157
1158     ContactCache results;
1159
1160     foreach(const QueryContext &context, m_queryContexts) {
1161         fetchResults(results, context);
1162     }
1163
1164     const bool calculateGlobalPresence(m_definitionHints.contains(QContactGlobalPresence::DefinitionName) || m_definitionHints.isEmpty());
1165     const bool calculateDisplayLabel(m_definitionHints.contains(QContactDisplayLabel::DefinitionName) || m_definitionHints.isEmpty());
1166     const bool calculateAvatar(m_definitionHints.contains(QContactAvatar::DefinitionName) || m_definitionHints.isEmpty());
1167
1168     // Update synthetic details and detail links
1169     for(ContactCache::Iterator c = results.begin(); c != results.end() ; ++c) {
1170         removeDuplicateDetails(c.value());
1171
1172         if (calculateGlobalPresence) {
1173             engine()->updateGlobalPresence(c.value());
1174         }
1175         if (calculateDisplayLabel) {
1176             engine()->updateDisplayLabel(c.value(), m_nameOrder);
1177         }
1178         if (calculateAvatar) {
1179             engine()->updateAvatar(c.value());
1180         }
1181
1182         updateDetailLinks(c.value());
1183     }
1184
1185     // Sort the contacts
1186     const QList<QContactLocalId> sortedIds =
1187             QContactManagerEngine::sortContacts(results.values(),
1188                                                 m_request->sorting());
1189
1190     foreach(const QContactLocalId localId, sortedIds) {
1191         m_contacts.append(results.value(localId));
1192     }
1193
1194     // Report the result
1195     return true;
1196 }
1197
1198 void
1199 QTrackerContactFetchRequest::emitResult(QContactManager::Error error)
1200 {
1201     engine()->updateContactFetchRequest(m_request, m_contacts, error,
1202                                        QContactAbstractRequest::FinishedState);
1203 }
1204
1205 ///////////////////////////////////////////////////////////////////////////////////////////////////
1206
1207 #include "moc_contactfetchrequest.cpp"