/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

//#define QT_SFW_SERVICEDATABASE_DEBUG

#include "servicedatabase_p.h"
#include <QDir>
#include <QSet>
#include "qserviceinterfacedescriptor.h"
#include "qserviceinterfacedescriptor_p.h"
#include <QUuid>
#include "dberror_p.h"

//database name
#define RESOLVERDATABASE "services.db"

//database table names
#define SERVICE_TABLE "Service"
#define INTERFACE_TABLE "Interface"
#define DEFAULTS_TABLE "Defaults"
#define SERVICE_PROPERTY_TABLE "ServiceProperty"
#define INTERFACE_PROPERTY_TABLE "InterfaceProperty"

//separator
#define RESOLVERDATABASE_PATH_SEPARATOR "//"

#ifdef QT_SFW_SERVICEDATABASE_DEBUG
#include <QDebug>
#endif

#define SERVICE_DESCRIPTION_KEY "DESCRIPTION"
#ifdef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
#define SECURITY_TOKEN_KEY "SECURITYTOKEN"
#endif
#define INTERFACE_DESCRIPTION_KEY "DESCRIPTION"
#define INTERFACE_CAPABILITY_KEY "CAPABILITIES"

QTM_BEGIN_NAMESPACE

enum TBindIndexes
    {
        EBindIndex=0,
        EBindIndex1,
        EBindIndex2,
        EBindIndex3,
        EBindIndex4,
        EBindIndex5,
        EBindIndex6,
        EBindIndex7
    };


/*
   \class ServiceDatabase
   The ServiceDatabase is responsible for the management of a single
   service database.  It provides operations for:
   - opening and closing a connection with the database,
   - registering and unregistering services
   - querying for services and interfaces
   - setting and getting default interface implementations.
*/

/*
    Constructor
*/
ServiceDatabase::ServiceDatabase(void)
:m_isDatabaseOpen(false),m_inTransaction(false)
{
}

/*
    Destructor
*/
ServiceDatabase::~ServiceDatabase()
{
    close();
}

/*
    Opens the service database
    The method creates or opens database and creates tables if they are not present
    Returns true if the operation was successful, false if not.
*/
bool ServiceDatabase::open()
{
    if (m_isDatabaseOpen)
        return true;

    QString path;

    //Create full path to database
    if(m_databasePath.isEmpty ())
        m_databasePath = databasePath();

    path = m_databasePath;
    QFileInfo dbFileInfo(path);
    if (!dbFileInfo.dir().exists()) {
       if(!QDir::root().mkpath(dbFileInfo.path())) {
           QString errorText("Could not create database directory: %1");
           m_lastError.setError(DBError::CannotCreateDbDir, errorText.arg(dbFileInfo.path()));
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
           qWarning() << "ServiceDatabase::open():-"
                        << "Problem:" << qPrintable(m_lastError.text());
#endif
           close();
           return false;
        }
    }

    m_connectionName = dbFileInfo.completeBaseName();
    QSqlDatabase  database;
    if(QSqlDatabase::contains(m_connectionName)) {
        database = QSqlDatabase::database(m_connectionName);
    } else {
        database = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
        database.setDatabaseName(path);
    }

    if (!database.isValid()){
        m_lastError.setError(DBError::InvalidDatabaseConnection);
        close();
        return false;
    }

    //Create or open database
    if (!database.isOpen()) {
        if(!database.open()) {
            m_lastError.setError(DBError::SqlError, database.lastError().text());
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::open():-"
                        << "Problem:" << "Could not open database"
                        << "\nReason:" << m_lastError.text();
#endif
            close();
            return false;
        }
    }
    m_isDatabaseOpen = true;

    //Check database structure (tables) and recreate tables if neccessary
    //If one of tables is missing remove all tables and recreate them
    //This operation is required in order to avoid data coruption
    if (!checkTables()) {
        if(dropTables()) {
            if (createTables()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qDebug() << "ServiceDatabase::open():-"
                    << "Database tables recreated";
#endif
            } else {
                //createTable() should've handled error message
                //and warning
                close();
                return false;
            }
        }
        else {
            //dropTables() should've handled error message
            //and warning
            close();
            return false;
        }
    }
    return true;
}

/*
   Adds a \a service into the database.

   May set the following error codes
   DBError::NoError
   DBError::LocationAlreadyRegistered
   DBError::IfaceImplAlreadyRegistered
   DBError::SqlError
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
//bool ServiceDatabase::registerService(ServiceMetaData &service)
bool ServiceDatabase::registerService(const ServiceMetaDataResults &service, const QString &securityToken)
{
#ifndef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    Q_UNUSED(securityToken);
#endif

    if(!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    if (!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << "Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }
    //See if the service's location has already been previously registered
    QString statement("SELECT Name from Service WHERE Location=? COLLATE NOCASE");
    QList<QVariant> bindValues;
    bindValues.append(service.location);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    if (query.next()) {
        QString alreadyRegisteredService = query.value(EBindIndex).toString();
        const QString errorText = "Cannot register service \"%1\". Service location \"%2\" is already "
                    "registered to service \"%3\".  \"%3\" must first be deregistered "
                    "for new registration to take place.";

        m_lastError.setError(DBError::LocationAlreadyRegistered,
                errorText.arg(service.name)
                        .arg(service.location)
                        .arg(alreadyRegisteredService));

        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

#ifdef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    statement = "SELECT Value FROM ServiceProperty WHERE ServiceID = ? AND Key = ?";
    bindValues.clear();
    bindValues.append(service.name);
    bindValues.append(SECURITY_TOKEN_KEY);
    
    if(!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QStringList securityTokens;
    while(query.next()) {
        securityTokens << query.value(EBindIndex).toString();
    }
    
    if (!securityTokens.isEmpty() && securityTokens.first() != securityToken) {
        QString errorText("Access denied: \"%1\"");
             m_lastError.setError(DBError::NoWritePermissions, errorText.arg(service.name));
             rollbackTransaction(&query);
     #ifdef QT_SFW_SERVICEDATABASE_DEBUG
             qWarning() << "ServiceDatabase::registerService():-"
                         << "Problem: Unable to register service"
                         << "\nReason:" << qPrintable(m_lastError.text());
     #endif    
             return false;
    }
#endif

    statement = "INSERT INTO Service(ID,Name,Location) VALUES(?,?,?)";
    QString serviceID = QUuid::createUuid().toString();

    bindValues.clear();
    bindValues.append(serviceID);
    bindValues.append(service.name);
    bindValues.append(service.location);

    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "INSERT INTO ServiceProperty(ServiceID,Key,Value) VALUES(?,?,?)";
    bindValues.clear();
    bindValues.append(serviceID);
    bindValues.append(SERVICE_DESCRIPTION_KEY);
    if (service.description.isNull())
        bindValues.append("");
    else
        bindValues.append(service.description);

    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::registerService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }
    
#ifdef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    if (securityTokens.isEmpty()) {
        statement = "INSERT INTO ServiceProperty(ServiceID,Key,Value) VALUES(?,?,?)";
        bindValues.clear();
        bindValues.append(service.name);
        bindValues.append(SECURITY_TOKEN_KEY);
        bindValues.append(securityToken);
    
        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::registerService():-"
                        << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }
#endif
    
    QList <QServiceInterfaceDescriptor> interfaces = service.interfaces;
    QString interfaceID;;
    foreach (const QServiceInterfaceDescriptor &interface, interfaces) {
        interfaceID = getInterfaceID(&query, interface);
        if (m_lastError.code() == DBError::NoError) {
            QString errorText;
            errorText = "Cannot register service \"%1\". \"%1\" is already registered "
                        "and implements interface \"%2\", Version \"%3.%4.\"  \"%1\" must "
                        "first be deregistered for new registration to take place.";
            m_lastError.setError(DBError::IfaceImplAlreadyRegistered,
                                errorText.arg(interface.serviceName())
                                            .arg(interface.interfaceName())
                                            .arg(interface.majorVersion())
                                            .arg(interface.minorVersion()));

            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::registerService():-"
                        << "Problem:" << qPrintable(m_lastError.text());
#endif
            return false;
        } else if (m_lastError.code() == DBError::NotFound){
            //No interface implementation already exists for the service
            //so add it
            if(!insertInterfaceData(&query, interface, serviceID)) {
                rollbackTransaction(&query);
                return false;
            } else {
                continue;
            }
        } else {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::registerService():-"
                        << "Unable to confirm if implementation version"
                        << (QString::number(interface.majorVersion()) + "."
                           + QString::number(interface.minorVersion())).toAscii()
                        << "for interface" << interface.interfaceName()
                        << "is already registered for service "
                        << interface.serviceName()
                        << qPrintable(QString("\n") + m_lastError.text());
#endif
            return false;
        }
    }

    interfaces = service.latestInterfaces;
    QServiceInterfaceDescriptor defaultInterface;
    foreach(const QServiceInterfaceDescriptor &interface, interfaces) {
        defaultInterface = interfaceDefault(interface.interfaceName(), NULL, true);
        if (m_lastError.code() == DBError::NoError
                || m_lastError.code() == DBError::ExternalIfaceIDFound) {
            continue; //default already exists so don't do anything
        } else if (m_lastError.code() == DBError::NotFound) {
            //default does not already exist so create one
            interfaceID = getInterfaceID(&query, interface);
            if (m_lastError.code() != DBError::NoError) {
                rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qWarning() << "ServiceDatabase::registerService():-"
                    << "Unable to retrieve interfaceID for "
                        "interface" << interface.interfaceName()
                        <<  qPrintable(QString("\n") + m_lastError.text());
#endif
                return false;
            }

            statement = "INSERT INTO Defaults(InterfaceName, InterfaceID) VALUES(?,?)";
            bindValues.clear();
            bindValues.append(interface.interfaceName());
            bindValues.append(interfaceID);
            if (!executeQuery(&query, statement, bindValues)) {
                rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qWarning() << "ServiceDatabase::registerService():-"
                    << qPrintable(m_lastError.text());
#endif
                return false;
            }
        } else {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::registerService()"
                        << "Problem: Unable to confirm if interface"
                        << interface.interfaceName()
                        << "already has a default implementation";
#endif
            return false;
        }
    }

    if(!commitTransaction(&query)) {
        rollbackTransaction(&query);
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Obtains an interface ID corresponding to a given interface \a descriptor

    May set the following error codes:
    DBError::NoError
    DBError::NotFound
    DBError::SqlError
    DBError::DatabaseNotOpen
    DBError::InvalidDatabaseConnection
*/
QString ServiceDatabase::getInterfaceID(const QServiceInterfaceDescriptor &descriptor) {
    QString interfaceID;
    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterfaceID():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return interfaceID;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    return getInterfaceID(&query, descriptor);
}

/*
    This function should only ever be called on a user scope database.
    It returns a list of Interface Name and Interface ID pairs, where
    the Interface ID refers to an external interface implementation
    in the system scope database.

    May set the last error to:
    DBError::NoError
    DBError::SqlError
    DBError::DatabaseNotOpen
    DBError::InvalidDatabaseConnection

    Aside:  There is only one query which implicitly gets
    wrapped in it's own transaction.
*/
QList<QPair<QString,QString> > ServiceDatabase::externalDefaultsInfo()
{
    QList<QPair<QString,QString> > ret;
    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::externalDefaultsInfo():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return ret;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    //Prepare search query, bind criteria values and execute search
    QString selectComponent = "SELECT InterfaceName, InterfaceID ";
    QString fromComponent = "FROM Defaults ";
    QString whereComponent = "WHERE InterfaceID NOT IN (SELECT Interface.ID FROM Interface) ";

    //Aside: this individual query is implicitly wrapped in a transaction
    if (!executeQuery(&query, selectComponent + fromComponent + whereComponent)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::externalDefaultsInfo():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return ret;
    }

    while (query.next()) {
        ret.append(qMakePair(query.value(EBindIndex).toString(),
                    query.value(EBindIndex1).toString()));
    }

    m_lastError.setError(DBError::NoError);
    return ret;
}

/*
    Helper function that obtains an interfaceID for a given \a descriptor.

    May set last error to one of the following error codes:
    DBError::NoError
    DBError::NotFound
    DBError::SqlError

    Aside: This function may be safely called standalone or within an explicit
    transaction.  If called standalone, it's single query is implicitly
    wrapped in it's own transaction.
*/
QString ServiceDatabase::getInterfaceID(QSqlQuery *query, const QServiceInterfaceDescriptor &interface)
{
    QString statement = "SELECT Interface.ID "
                        "FROM Interface, Service "
                        "WHERE Service.ID = Interface.ServiceID "
                        "AND Service.Name = ? COLLATE NOCASE "
                        "AND Interface.Name = ? COLLATE NOCASE "
                        "AND Interface.VerMaj = ? AND Interface.VerMin = ?";
    QList<QVariant> bindValues;
    bindValues.append(interface.serviceName());
    bindValues.append(interface.interfaceName());
    bindValues.append(interface.majorVersion());
    bindValues.append(interface.minorVersion());

    if (!executeQuery(query, statement, bindValues)) {
        return QString();
    }

    if (!query->next()) {
         QString errorText("No Interface Descriptor found with "
                            "\nService name: %1 "
                            "\nInterface name: %2 "
                            "\nVersion: %3.%4");
        m_lastError.setError(DBError::NotFound, errorText.arg(interface.serviceName())
                                                        .arg(interface.interfaceName())
                                                        .arg(interface.majorVersion())
                                                        .arg(interface.minorVersion()));
        return QString();
    }

    m_lastError.setError(DBError::NoError);
    return query->value(EBindIndex).toString();
}

/*
    Helper functions that saves \a interface related data in the Interface table
    The \a interface data is recorded as belonging to the service assocciated
    with \a serviceID.

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError

    Aside: It is already assumed that a write transaction has been started by the
    time this function is called; and this function will not rollback/commit
    the transaction.
*/
bool ServiceDatabase::insertInterfaceData(QSqlQuery *query,const QServiceInterfaceDescriptor &interface, const QString &serviceID)
{
    QString statement = "INSERT INTO Interface(ID, ServiceID,Name,VerMaj, VerMin) "
                        "VALUES(?,?,?,?,?)";
    QString interfaceID = QUuid::createUuid();

    QList<QVariant> bindValues;
    bindValues.append(interfaceID);
    bindValues.append(serviceID);
    bindValues.append(interface.interfaceName());
    bindValues.append(interface.majorVersion());
    bindValues.append(interface.minorVersion());

    if (!executeQuery(query, statement, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::insertInterfaceData():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "INSERT INTO InterfaceProperty(InterfaceID, Key, Value) VALUES(?,?,?)";
    QHash<QServiceInterfaceDescriptor::Attribute, QVariant>::const_iterator iter = interface.d->attributes.constBegin();
    bool isValidInterfaceProperty;
    QString capabilities;
    QString interfaceDescription;
    while (iter != interface.d->attributes.constEnd()) {
        isValidInterfaceProperty = true;

        bindValues.clear();
        bindValues.append(interfaceID);
        switch (iter.key()) {
            case (QServiceInterfaceDescriptor::Capabilities):
                bindValues.append(INTERFACE_CAPABILITY_KEY);
                capabilities = interface.attribute(QServiceInterfaceDescriptor::Capabilities).toStringList().join(",");
                if (capabilities.isNull())
                    capabilities = "";
                bindValues.append(capabilities);
                break;
            case(QServiceInterfaceDescriptor::Location):
                isValidInterfaceProperty = false;
                break;
            case(QServiceInterfaceDescriptor::ServiceDescription):
                isValidInterfaceProperty = false;
                break;
            case(QServiceInterfaceDescriptor::InterfaceDescription):
                bindValues.append(INTERFACE_DESCRIPTION_KEY);
                interfaceDescription = interface.attribute(QServiceInterfaceDescriptor::InterfaceDescription).toString();
                if (interfaceDescription.isNull())
                    interfaceDescription = "";
                bindValues.append(interfaceDescription);
                break;
            default:
                isValidInterfaceProperty = false;
                break;
        }

        if (isValidInterfaceProperty) {
              if (!executeQuery(query, statement, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                  qWarning() << "ServiceDatabase::insertInterfaceData():-"
                                << qPrintable(m_lastError.text());
#endif
                  return false;
              }
        }
        ++iter;
    }

    //add custom attributes
    QHash<QString, QString>::const_iterator customIter = interface.d->customAttributes.constBegin();
    while(customIter!=interface.d->customAttributes.constEnd()) {
        bindValues.clear();
        bindValues.append(interfaceID);
        //to avoid key clashes use separate c_ namespace ->is this sufficient?
        bindValues.append("c_"+customIter.key());
        bindValues.append(customIter.value());
        if (!executeQuery(query, statement, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::insertInterfaceData(customProps):-"
                            << qPrintable(m_lastError.text());
#endif
            return false;
        }
        ++customIter;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Helper function that executes the sql query specified in \a statement.
    It is assumed that the \a statement uses positional placeholders and
    corresponding parameters are placed in the list of \a bindValues.

    Aside: This function may be safely called standalone or within an explicit
    transaction.  If called standalone, it's single query is implicitly
    wrapped in it's own transaction.

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
    DBError::NoWritePermissions
    DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::executeQuery(QSqlQuery *query, const QString &statement, const QList<QVariant> &bindValues)
{
    Q_ASSERT(query != NULL);

    bool success = false;
    enum {Prepare =0 , Execute=1};
    for(int stage=Prepare; stage <= Execute; ++stage) {
        if ( stage == Prepare)
            success = query->prepare(statement);
        else // stage == Execute
            success = query->exec();

        if (!success) {
            QString errorText;
            errorText = "Problem: Could not %1 statement: %2"
                "\nReason: %3"
                "\nParameters: %4\n";
            QString parameters;
            if (bindValues.count() > 0) {
                for(int i = 0; i < bindValues.count(); ++i) {
                    parameters.append(QString("\n\t[") + QString::number(i) + "]: " + bindValues.at(i).toString());
                }
            } else {
                parameters = "None";
            }

            DBError::ErrorCode errorType;
            int result = query->lastError().number();
            if (result == 26 || result == 11) {//SQLILTE_NOTADB || SQLITE_CORRUPT
                qWarning() << "Service Framework:- Database file is corrupt or invalid:" << databasePath();
                errorType = DBError::InvalidDatabaseFile;
            }
            else if ( result == 8) //SQLITE_READONLY
                errorType = DBError::NoWritePermissions;
            else
                errorType = DBError::SqlError;

            m_lastError.setError(errorType,
                    errorText
                    .arg(stage == Prepare ?"prepare":"execute")
                    .arg(statement)
                    .arg(query->lastError().text())
                    .arg(parameters));

            query->finish();
            query->clear();
            return false;
        }

        if (stage == Prepare) {
            foreach(const QVariant &bindValue, bindValues)
            query->addBindValue(bindValue);
        }
    }

    m_lastError.setError(DBError::NoError);
    return true;
}

/*
   Obtains a list of QServiceInterfaceDescriptors that match the constraints supplied
   by \a filter.

   May set last error to one of the following error codes:
   DBError::NoError
   DBError::SqlError
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
QList<QServiceInterfaceDescriptor> ServiceDatabase::getInterfaces(const QServiceFilter &filter)
{
    QList<QServiceInterfaceDescriptor> interfaces;
    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterfaces():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return interfaces;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    //multiple read queries are performed so wrap them
    //in a read only transaction
    if (!beginTransaction(&query, Read)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterfaces():-"
                    << "Unable to begin transaction:"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return interfaces;
    }

    //Prepare search query, bind criteria values
    QString selectComponent = "SELECT Interface.Name, "
                                "Service.Name, Interface.VerMaj, "
                                "Interface.VerMin, "
                                "Service.Location, "
                                "Service.ID, "
                                "Interface.ID ";
    QString fromComponent = "FROM Interface, Service ";
    QString whereComponent = "WHERE Service.ID = Interface.ServiceID ";
    QList<QVariant> bindValues;

    if (filter.serviceName().isEmpty() && filter.interfaceName().isEmpty()) {
        //do nothing, (don't add any extra constraints to the query
    } else {

        if (!filter.serviceName().isEmpty()) {
            whereComponent.append("AND Service.Name = ?").append(" COLLATE NOCASE ");
            bindValues.append(filter.serviceName());
        }
        if (!filter.interfaceName().isEmpty()) {
            whereComponent.append("AND Interface.Name = ?").append(" COLLATE NOCASE ");
            bindValues.append(filter.interfaceName());
            if (filter.majorVersion() >=0 && filter.minorVersion() >=0) {
                if (filter.versionMatchRule() == QServiceFilter::ExactVersionMatch) {
                    whereComponent.append("AND Interface.VerMaj = ?").append(" AND Interface.VerMin = ? ");
                    bindValues.append(QString::number(filter.majorVersion()));
                    bindValues.append(QString::number(filter.minorVersion()));
                }
                else if (filter.versionMatchRule() == QServiceFilter::MinimumVersionMatch) {
                    whereComponent.append("AND ((Interface.VerMaj > ?")
                        .append(") OR Interface.VerMaj = ?").append(" AND Interface.VerMin >= ?").append(") ");
                    bindValues.append(QString::number(filter.majorVersion()));
                    bindValues.append(QString::number(filter.majorVersion()));
                    bindValues.append(QString::number(filter.minorVersion()));
                }
            }
        }
    }

    if (!executeQuery(&query, selectComponent + fromComponent + whereComponent, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterfaces():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        rollbackTransaction(&query);
        return interfaces;
    }

    QServiceInterfaceDescriptor interface;
    interface.d = new QServiceInterfaceDescriptorPrivate;
    QStringList capabilities;
    QString serviceID;
    QString interfaceID;
    const QSet<QString> filterCaps = filter.capabilities().toSet();
    QSet<QString> difference;

    while(query.next()){
        difference.clear();
        interface.d->customAttributes.clear();
        interface.d->attributes.clear();
        interface.d->interfaceName =query.value(EBindIndex).toString();
        interface.d->serviceName = query.value(EBindIndex1).toString();
        interface.d->major = query.value(EBindIndex2).toInt();
        interface.d->minor = query.value(EBindIndex3).toInt();

        interface.d->attributes[QServiceInterfaceDescriptor::Location]
            = query.value(EBindIndex4).toString();

        serviceID = query.value(EBindIndex5).toString();
        if (!populateServiceProperties(&interface, serviceID)) {
            //populateServiceProperties should already give a warning message
            //and set the last error
            interfaces.clear();
            rollbackTransaction(&query);
            return interfaces;
        }

        interfaceID = query.value(EBindIndex6).toString();
        if (!populateInterfaceProperties(&interface, interfaceID)) {
            //populateInterfaceProperties should already give a warning message
            //and set the last error
            interfaces.clear();
            rollbackTransaction(&query);
            return interfaces;
        }

        const QSet<QString> ifaceCaps = interface.d->attributes.value(QServiceInterfaceDescriptor::Capabilities).toStringList().toSet();
        difference = ((filter.capabilityMatchRule() == QServiceFilter::MatchMinimum) ? (filterCaps-ifaceCaps) : (ifaceCaps-filterCaps));
        if (!difference.isEmpty())
            continue;

        //only return those interfaces that comply with set custom filters
        if (filter.customAttributes().size() > 0) {
            QSet<QString> keyDiff = filter.customAttributes().toSet();
            keyDiff.subtract(interface.d->customAttributes.uniqueKeys().toSet());
            if (keyDiff.isEmpty()) { //target descriptor has same custom keys as filter
                bool isMatch = true;
                const QStringList keys = filter.customAttributes();
                for(int i = 0; i<keys.count(); i++) {
                    if (interface.d->customAttributes.value(keys[i]) !=
                            filter.customAttribute(keys[i])) {
                        isMatch = false;
                        break;
                    }
                }
                if (isMatch)
                    interfaces.append(interface);
            }
        } else { //no custom keys -> SQL statement ensures proper selection already
            interfaces.append(interface);
        }
    }

    rollbackTransaction(&query);//read-only operation so just rollback
    m_lastError.setError(DBError::NoError);
    return interfaces;
}

/*
   Obtains a QServiceInterfaceDescriptor that
   corresponds to a given \a interfaceID

   May set last error to one of the following error codes:
   DBError::NoError
   DBError::NotFound
   DBError::SqlError
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
QServiceInterfaceDescriptor ServiceDatabase::getInterface(const QString &interfaceID)
{
    QServiceInterfaceDescriptor interface;
    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterface():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    if (!beginTransaction(&query, Read)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterface():-"
                    << "Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    QString selectComponent = "SELECT Interface.Name, "
                                "Service.Name, Interface.VerMaj, "
                                "Interface.VerMin, "
                                "Service.Location, "
                                "Service.ID ";
    QString fromComponent = "FROM Interface, Service ";
    QString whereComponent = "WHERE Service.ID = Interface.ServiceID "
                                    "AND Interface.ID = ? ";
    QList<QVariant> bindValues;
    bindValues.append(interfaceID);

    if (!executeQuery(&query, selectComponent + fromComponent + whereComponent, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getInterfaces():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    if (!query.next()) {
        rollbackTransaction(&query);
        QString errorText("Interface implementation not found for Interface ID: %1");
        m_lastError.setError(DBError::NotFound, errorText.arg(interfaceID));
        return interface;
    }

    interface.d = new QServiceInterfaceDescriptorPrivate;
    interface.d->interfaceName = query.value(EBindIndex).toString();
    interface.d->serviceName = query.value(EBindIndex1).toString();
    interface.d->major = query.value(EBindIndex2).toInt();
    interface.d->minor = query.value(EBindIndex3).toInt();
    interface.d->attributes[QServiceInterfaceDescriptor::Location]
        = query.value(EBindIndex4).toString();

    QString serviceID = query.value(EBindIndex5).toString();
    if (!populateServiceProperties(&interface, serviceID)) {
        //populateServiceProperties should already give a warning message
        //and set the last error
        rollbackTransaction(&query);
        return QServiceInterfaceDescriptor();
    }

    if (!populateInterfaceProperties(&interface, interfaceID)) {
        //populateInterfaceProperties should already give a warning message
        //and set the last error
        rollbackTransaction(&query);
        return QServiceInterfaceDescriptor();
    }

    rollbackTransaction(&query);//read only operation so just rollback
    m_lastError.setError(DBError::NoError);
    return interface;
}

/*
    Obtains a list of services names.  If \a interfaceName is empty,
    then all service names are returned.  If \a interfaceName specifies
    an interface then the names of all services implementing that interface
    are returned

    May set last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
    DBError::DatabaseNotOpen
    DBError::InvalidDatabaseConnection
    DBError::NoWritePermissions
    DBError::InvalidDatabaseFile

    Aside:  There is only one query which implicitly gets
    wrapped in it's own transaction.
*/
QStringList ServiceDatabase::getServiceNames(const QString &interfaceName)
{
    QStringList services;
    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getServiceNames():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return services;
    }
    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);
    QString selectComponent("SELECT DISTINCT Service.Name COLLATE NOCASE ");
    QString fromComponent;
    QString whereComponent;
    QList<QVariant> bindValues;
    if (interfaceName.isEmpty()) {
        fromComponent = "FROM Service ";
    } else {
        fromComponent = "FROM Interface,Service ";
        whereComponent = "WHERE Service.ID = Interface.ServiceID AND Interface.Name = ? COLLATE NOCASE ";
        bindValues.append(interfaceName);
    }

    if (!executeQuery(&query, selectComponent + fromComponent + whereComponent, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::getServiceNames():-"
                    << qPrintable(m_lastError.text());
#endif
        return services;
    }

    while( query.next()) {
        services.append(query.value(EBindIndex).toString());
    }
    query.finish();
    query.clear();
    m_lastError.setError(DBError::NoError);
    return services;
}

/*
    Returns a descriptor for the default interface implementation of
    \a interfaceName.

    For user scope databases only, \a defaultInterfaceID is set if the default
    in the user scope database refers to a interface implementation in the
    system scope database.  In this case the descriptor will be invalid and
    the \a defaultInterfaceID must be used to query the system scope database,
    The last error set to DBError::ExternalIfaceIDFound

    If this function is called within a transaction, \a inTransaction
    must be set to true.  If \a inTransaction is false, this fuction
    will begin and end its own transaction.

    The last error may be set to one of the following error codes:
    DBError::NoError
    DBError::ExternalIfaceIDFound
    DBError::SqlError
    DBError::DatabaseNotOpen
    DBError::InvalidDatabaseConnection
    DBError::NoWritePermissions
    DBError::InvalidDatabaseFile
*/
QServiceInterfaceDescriptor ServiceDatabase::interfaceDefault(const QString &interfaceName, QString *defaultInterfaceID,
                                                                    bool inTransaction)
{
    QServiceInterfaceDescriptor interface;
    if (!checkConnection())
    {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::interfaceDefault():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    if (!inTransaction && !beginTransaction(&query, Read)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::interfaceDefault(QString, QString):-"
                    << "Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    QString statement("SELECT InterfaceID FROM Defaults WHERE InterfaceName = ? COLLATE NOCASE");
    QList<QVariant> bindValues;
    bindValues.append(interfaceName);
    if(!executeQuery(&query, statement, bindValues)) {
        if (!inTransaction)
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::interfaceDefault():-"
                    << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    QString interfaceID;
    if (!query.next())
    {
        if (!inTransaction)
            rollbackTransaction(&query);
        QString errorText("No default service found for interface: \"%1\"");
        m_lastError.setError(DBError::NotFound, errorText.arg(interfaceName));
        return interface;
    }
    else
        interfaceID = query.value(EBindIndex).toString();
    Q_ASSERT(!interfaceID.isEmpty());

    statement = "SELECT Interface.Name, "
                        "Service.Name, Interface.VerMaj, "
                        "Interface.VerMin, "
                        "Service.Location, "
                        "Service.ID "
                    "FROM Service, Interface "
                    "WHERE Service.ID = Interface.ServiceID AND Interface.ID = ?";
    bindValues.clear();
    bindValues.append(interfaceID);
    if(!executeQuery(&query, statement, bindValues))
    {
        if (!inTransaction)
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::interfaceDefault():-"
                    << qPrintable(m_lastError.text());
#endif
        return interface;
    }

    if(!query.next()) {
        if (!inTransaction)
            rollbackTransaction(&query);
        if (defaultInterfaceID != NULL )
            *defaultInterfaceID = interfaceID;
        m_lastError.setError(DBError::ExternalIfaceIDFound);
        return interface;
    }

    interface.d = new QServiceInterfaceDescriptorPrivate;
    interface.d->interfaceName =query.value(EBindIndex).toString();
    interface.d->serviceName = query.value(EBindIndex1).toString();
    interface.d->major = query.value(EBindIndex2).toInt();
    interface.d->minor = query.value(EBindIndex3).toInt();

    interface.d->attributes[QServiceInterfaceDescriptor::Location]
        = query.value(EBindIndex4).toString();

    QString serviceID = query.value(EBindIndex5).toString();
    if (!populateServiceProperties(&interface, serviceID)) {
        //populateServiceProperties should already give a warning
        //and set the last error
        if (!inTransaction)
            rollbackTransaction(&query);
        return QServiceInterfaceDescriptor();
    }

    if (!populateInterfaceProperties(&interface, interfaceID)) {
        //populateInterfaceProperties should already give a warning
        //and set the last error
        if (!inTransaction)
            rollbackTransaction(&query);
        return QServiceInterfaceDescriptor();
    }

    if (!inTransaction)
        rollbackTransaction(&query); //Read only operation so just rollback
    m_lastError.setError(DBError::NoError);
    return interface;
}

/*
   Sets a particular service's \a interface implementation as a the default
   implementation to look up when using the interface's name in
   interfaceDefault().

   For a user scope database an \a externalInterfaceID can be provided
   so that the Defaults table will contain a "link" to an interface
   implmentation provided in the system scope database.

   May set the last error to one of the following error codes:
   DBError::NoError
   DBerror::NotFound
   DBError::SqlError
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::setInterfaceDefault(const QServiceInterfaceDescriptor &interface, const QString &externalInterfaceID)
{
    if(!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
            << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    //Begin Transaction
    if(!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
            << "Problem: Unable to begin transaction"
            << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QString statement;
    QList<QVariant> bindValues;
    QString interfaceID = externalInterfaceID;
    if (interfaceID.isEmpty()) {
        statement = "SELECT Interface.ID from Interface, Service "
                "WHERE Service.ID = Interface.ServiceID "
                "AND Service.Name = ? COLLATE NOCASE "
                "AND Interface.Name = ? COLLATE NOCASE "
                "AND Interface.VerMaj = ? "
                "AND Interface.VerMin = ? ";
        bindValues.append(interface.serviceName());
        bindValues.append(interface.interfaceName());
        bindValues.append(interface.majorVersion());
        bindValues.append(interface.minorVersion());

        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
                << qPrintable(m_lastError.text());
#endif
            return false;
        }

        if(!query.next()) {
            QString errorText;
            errorText = "No implementation for interface: %1, Version: %2.%3 found "
                "for service: %4";
            m_lastError.setNotFoundError(errorText.arg(interface.interfaceName())
                    .arg(interface.majorVersion())
                    .arg(interface.minorVersion())
                    .arg(interface.serviceName()));

            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatbase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
                << "Problem: Unable to set default service"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
            return false;
        }

        interfaceID = query.value(EBindIndex).toString();
        Q_ASSERT(!interfaceID.isEmpty());
    }

    statement = "SELECT InterfaceName FROM Defaults WHERE InterfaceName = ? COLLATE NOCASE";
    bindValues.clear();
    bindValues.append(interface.interfaceName());
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    if (query.next()) {
        statement = "UPDATE Defaults "
            "SET InterfaceID = ? "
            "WHERE InterfaceName = ? COLLATE NOCASE";
        bindValues.clear();
        bindValues.append(interfaceID);
        bindValues.append(interface.interfaceName());

        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
                << qPrintable(m_lastError.text());
#endif
            return false;
        }
    } else {
        statement = "INSERT INTO Defaults(InterfaceName,InterfaceID) VALUES(?,?)";
        bindValues.clear();
        bindValues.append(interface.interfaceName());
        bindValues.append(interfaceID);

        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::setInterfaceDefault(QServiceInterfaceDescriptor):-"
                << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }

    //End Transaction
    if (!commitTransaction(&query)) {
        rollbackTransaction(&query);
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
   Removes the service with name \a serviceName.
   If the service provides a default interface implementation, then
   another service implementing the highest interface implementation
   version becomes the new default(if any).  If more than one service
   provides same the highest version number, an arbitrary choice is made
   between them.

   May set the last error to the folowing error codes:
   DBError::NoError
   DBError::NotFound
   DBError::SqlError
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::unregisterService(const QString &serviceName, const QString &securityToken)
{
#ifndef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    Q_UNUSED(securityToken);
#endif

    if (!checkConnection()) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    if(!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << "Problem: Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }
        
    QString statement("SELECT Service.ID from Service WHERE Service.Name = ? COLLATE NOCASE");
    QList<QVariant> bindValues;
    bindValues.append(serviceName);
    if(!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QStringList serviceIDs;
    while(query.next()) {
        serviceIDs << query.value(EBindIndex).toString();
    }


#ifdef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    statement = "SELECT Value FROM ServiceProperty WHERE ServiceID = ? AND Key = ?";
    bindValues.clear();
    bindValues.append(serviceName);
    bindValues.append(SECURITY_TOKEN_KEY);
    if(!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QStringList securityTokens;
    while(query.next()) {
        securityTokens << query.value(EBindIndex).toString();
    }
    
    if (!securityTokens.isEmpty() && (securityTokens.first() != securityToken)) {
        QString errorText("Access denied: \"%1\"");
             m_lastError.setError(DBError::NoWritePermissions, errorText.arg(serviceName));
             rollbackTransaction(&query);
     #ifdef QT_SFW_SERVICEDATABASE_DEBUG
             qWarning() << "ServiceDatabase::unregisterService():-"
                         << "Problem: Unable to unregister service"
                         << "\nReason:" << qPrintable(m_lastError.text());
     #endif    
    }
    
#endif
    
    statement = "SELECT Interface.ID from Interface, Service "
                "WHERE Interface.ServiceID = Service.ID "
                    "AND Service.Name =? COLLATE NOCASE";
    bindValues.clear();
    bindValues.append(serviceName);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }
    
    QStringList interfaceIDs;
    while (query.next()) {
        interfaceIDs << query.value(EBindIndex).toString();
    }

    if (serviceIDs.count() == 0) {
        QString errorText("Service not found: \"%1\"");
        m_lastError.setError(DBError::NotFound, errorText.arg(serviceName));
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << "Problem: Unable to unregister service"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "SELECT Defaults.InterfaceName "
                "FROM Defaults, Interface, Service "
                "WHERE Defaults.InterfaceID = Interface.ID "
                    "AND Interface.ServiceID = Service.ID "
                    "AND Service.Name = ? COLLATE NOCASE";
    bindValues.clear();
    bindValues.append(serviceName);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase:unregisterService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QStringList serviceDefaultInterfaces;
    while(query.next()) {
        serviceDefaultInterfaces << query.value(EBindIndex).toString();
    }

    
    statement = "DELETE FROM Service WHERE Service.Name = ? COLLATE NOCASE";
    bindValues.clear();
    bindValues.append(serviceName);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::unregisterService():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "DELETE FROM Interface WHERE Interface.ServiceID = ?";
    foreach(const QString &serviceID, serviceIDs) {
        bindValues.clear();
        bindValues.append(serviceID);
        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::unregisterService():-"
                        << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }

#ifndef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
    statement = "DELETE FROM ServiceProperty WHERE ServiceID = ?";
#else
    statement = "DELETE FROM ServiceProperty WHERE ServiceID = ? AND Key <> ?";
#endif

    
    foreach(const QString &serviceID, serviceIDs) {
        bindValues.clear();
        bindValues.append(serviceID);
#ifdef QT_SFW_SERVICEDATABASE_USE_SECURITY_TOKEN
        bindValues.append(SECURITY_TOKEN_KEY);
#endif
        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::unregisterService():-"
                        << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }

    statement = "DELETE FROM InterfaceProperty WHERE InterfaceID = ?";
    foreach(const QString &interfaceID,  interfaceIDs) {
        bindValues.clear();
        bindValues.append(interfaceID);
        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::unregisterService():-"
                        << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }

    foreach(const QString &interfaceName, serviceDefaultInterfaces) {
        statement = "SELECT ID FROM Interface WHERE Interface.Name = ? COLLATE NOCASE "
                    "ORDER BY Interface.VerMaj DESC, Interface.VerMin DESC";
        bindValues.clear();
        bindValues.append(interfaceName);
        if (!executeQuery(&query, statement, bindValues)) {
            rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::unregisterService():-"
                        << qPrintable(m_lastError.text());
#endif
            return false;
        }

        if(query.next()) {
            QString newDefaultID = query.value(EBindIndex).toString();
            statement = "UPDATE Defaults SET InterfaceID = ? WHERE InterfaceName = ? COLLATE NOCASE ";
            bindValues.clear();
            bindValues.append(newDefaultID);
            bindValues.append(interfaceName);
            if (!executeQuery(&query, statement, bindValues)) {
                rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qWarning() << "ServiceDatabase::unregisterService():-"
                            << qPrintable(m_lastError.text());
#endif
                return false;
            }
        } else {
            statement = "DELETE FROM Defaults WHERE InterfaceName = ? COLLATE NOCASE ";
            bindValues.clear();
            bindValues.append(interfaceName);
            if (!executeQuery(&query, statement, bindValues)) {
                rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qWarning() << "ServiceDatabase::unregisterService():-"
                            << qPrintable(m_lastError.text());
#endif
                return false;
            }
        }
    }

    //databaseCommit
    if (!commitTransaction(&query)) {
        rollbackTransaction(&query);
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Closes the database

    May set the following error codes:
    DBError::NoError
    DBError::InvalidDatabaseConnection
*/
bool ServiceDatabase::close()
{
    if(m_isDatabaseOpen) {
        QSqlDatabase database = QSqlDatabase::database(m_connectionName, false);
        if (database.isValid()){
            if(database.isOpen()) {
                database.close();
                m_isDatabaseOpen = false;
                return true;
            }
        } else {
            m_lastError.setError(DBError::InvalidDatabaseConnection);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::close():-"
                        << "Problem: " << qPrintable(m_lastError.text());
#endif
            return false;
        }
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Sets the path of the service database to \a databasePath
*/
void ServiceDatabase::setDatabasePath(const QString &databasePath)
{
    m_databasePath = QDir::toNativeSeparators(databasePath);
}

/*
    Returns the path of the service database
*/
QString ServiceDatabase::databasePath() const
{
    QString path;
    if(m_databasePath.isEmpty()) {
        QSettings settings(QSettings::SystemScope, "Nokia", "Services");
        path = settings.value("ServicesDB/Path").toString();
        if (path.isEmpty()) {
            path = QDir::currentPath();
            if (path.lastIndexOf(RESOLVERDATABASE_PATH_SEPARATOR) != path.length() -1) {
                path.append(RESOLVERDATABASE_PATH_SEPARATOR);
            }
            path.append(RESOLVERDATABASE);
        }
        path = QDir::toNativeSeparators(path);
    } else {
        path = m_databasePath;
    }

    return path;
}

/*
    Helper method that creates the database tables: Service, Interface,
    Defaults, ServiceProperty and InterfaceProperty

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
    DBError::NoWritePermissions
    DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::createTables()
{
    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    //Begin Transaction
    if (!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << "Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QString statement("CREATE TABLE Service("
                        "ID TEXT NOT NULL PRIMARY KEY UNIQUE,"
                        "Name TEXT NOT NULL, "
                        "Location TEXT NOT NULL)");
    if (!executeQuery(&query, statement)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "CREATE TABLE Interface("
                "ID TEXT NOT NULL PRIMARY KEY UNIQUE,"
                "ServiceID TEXT NOT NULL, "
                "Name TEXT NOT NULL, "
                "VerMaj INTEGER NOT NULL, "
                "VerMin INTEGER NOT NULL)";
    if (!executeQuery(&query, statement)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "CREATE TABLE Defaults("
                "InterfaceName TEXT PRIMARY KEY UNIQUE NOT NULL,"
                "InterfaceID TEXT NOT NULL)";
    if (!executeQuery(&query, statement)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "CREATE TABLE ServiceProperty("
                "ServiceID TEXT NOT NULL,"
                "Key TEXT NOT NULL,"
                "Value TEXT NOT NULL)";
    if (!executeQuery(&query, statement)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    statement = "CREATE TABLE InterfaceProperty("
                "InterfaceID TEXT NOT NULL,"
                "Key TEXT NOT NULL,"
                "Value TEXT NOT NULL)";

    if (!executeQuery(&query, statement)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::createTables():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    if(!commitTransaction(&query)) {
        rollbackTransaction(&query);
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*!
    Helper method that checks if the all expected tables exist in the database
    Returns true if they all exist and false if any of them don't
*/
bool ServiceDatabase::checkTables()
{
    bool bTables(false);
    QStringList tables = QSqlDatabase::database(m_connectionName).tables();
    if (tables.contains(SERVICE_TABLE)
        && tables.contains(INTERFACE_TABLE)
        && tables.contains(DEFAULTS_TABLE)
        && tables.contains(SERVICE_PROPERTY_TABLE)
        && tables.contains(INTERFACE_PROPERTY_TABLE)){
            bTables = true;
    }
    return bTables;
}

/*
   This function should only ever be used on a user scope database
   It removes an entry from the Defaults table where the default
   refers to an interface implementation in the system scope database.
   The particular default that is removed is specified by
   \a interfaceID.

   May set the last error to one of the following error codes:
   DBError::NoError
   DBError::IfaceIDNotExternal
   DBError::SqlError
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::removeExternalDefaultServiceInterface(const QString &interfaceID)
{
    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);

    //begin transaction
    if (!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::removeExternalDefaultServiceInterface():-"
                    << "Problem: Unable to begin transaction"
                    << "\nReason:" << qPrintable(m_lastError.text());
#endif
        return false;
    }

    QString statement("SELECT Name FROM Interface WHERE Interface.ID = ?");
    QList<QVariant> bindValues;
    bindValues.append(interfaceID);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::removeDefaultServiceInterface():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }
    if (query.next()) {
        QString interfaceName = query.value(EBindIndex).toString();
        QString errorText("Local interface implementation exists for interface \"%1\" "
                           "with interfaceID: \"%2\"");
        m_lastError.setError(DBError::IfaceIDNotExternal,
                errorText.arg(interfaceName).arg(interfaceID));
        rollbackTransaction(&query);
        return false;
    }

    statement = "DELETE FROM Defaults WHERE InterfaceID = ? COLLATE NOCASE";
    bindValues.clear();
    bindValues.append(interfaceID);
    if (!executeQuery(&query, statement, bindValues)) {
        rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::removeDefaultServiceInterface():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    //end transaction
    if (!commitTransaction(&query)){
        rollbackTransaction(&query);
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Removes all tables from the database

    In future this function may be deprecated or removed.

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
    DBError::NoWritePermissions
    DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::dropTables()
{
    //Execute transaction for deleting the database tables
    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
    QSqlQuery query(database);
    QStringList expectedTables;
    expectedTables << SERVICE_TABLE
                << INTERFACE_TABLE
                << DEFAULTS_TABLE
                << SERVICE_PROPERTY_TABLE
                << INTERFACE_PROPERTY_TABLE;

    if (database.tables().count() > 0) {
        if (!beginTransaction(&query, Write)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
            qWarning() << "ServiceDatabase::dropTables():-"
                        << "Unable to begin transaction"
                        << "\nReason:" << qPrintable(m_lastError.text());
#endif
            return false;
        }
        QStringList actualTables = database.tables();

        foreach(QString expectedTable, expectedTables) {
            if ((actualTables.contains(expectedTable))
                && (!executeQuery(&query, QString("DROP TABLE ") + expectedTable))) {
                rollbackTransaction(&query);
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
                qWarning() << "ServiceDatabase::dropTables():-"
                           << qPrintable(m_lastError.text());
#endif
                return false;
            }
        }
        if (!commitTransaction(&query)) {
            rollbackTransaction(&query);
            return false;
        }
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Checks if the database is open
*/
bool ServiceDatabase::isOpen() const
{
  return m_isDatabaseOpen;
}

/*
   Checks the database connection.

   May set the last error to one of the following error codes:
   DBError::DatabaseNotOpen
   DBError::InvalidDatabaseConnection
*/
bool ServiceDatabase::checkConnection()
{
    if(!m_isDatabaseOpen)
    {
        m_lastError.setError(DBError::DatabaseNotOpen);
        return false;
    }

    if (!QSqlDatabase::database(m_connectionName).isValid())
    {
        m_lastError.setError(DBError::InvalidDatabaseConnection);
        return false;
    }

    return true;
}

/*
   Begins a transcaction based on the \a type which can be Read or Write.

   May set the last error to one of the following error codes:
   DBError::NoError
   DBError::SqlError
   DBError::NoWritePermissions
   DBError::InvalidDatabaseFile
*/
bool ServiceDatabase::beginTransaction(QSqlQuery *query, TransactionType type)
{
    bool success;
    if (type == Read)
        success = query->exec(QLatin1String("BEGIN"));
    else
        success = query->exec(QLatin1String("BEGIN IMMEDIATE"));

    if (!success) {
        int result = query->lastError().number();
        if (result == 26 || result == 11) {//SQLITE_NOTADB || SQLITE_CORRUPT
            qWarning() << "Service Framework:- Database file is corrupt or invalid:" << databasePath();
            m_lastError.setError(DBError::InvalidDatabaseFile, query->lastError().text());
        }
        else if (result == 8) { //SQLITE_READONLY
            qWarning() << "Service Framework:-  Insufficient permissions to write to database:" << databasePath();
            m_lastError.setError(DBError::NoWritePermissions, query->lastError().text());
        }
        else
            m_lastError.setError(DBError::SqlError, query->lastError().text());
        return false;
    }

    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Commits a transaction

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
*/
bool ServiceDatabase::commitTransaction(QSqlQuery *query)
{
    Q_ASSERT(query != NULL);
    query->finish();
    query->clear();
    if (!query->exec(QLatin1String("COMMIT"))) {
        m_lastError.setError(DBError::SqlError, query->lastError().text());
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Rolls back a transaction

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
*/
bool ServiceDatabase::rollbackTransaction(QSqlQuery *query)
{
    Q_ASSERT(query !=NULL);
    query->finish();
    query->clear();

    if (!query->exec(QLatin1String("ROLLBACK"))) {
        m_lastError.setError(DBError::SqlError, query->lastError().text());
        return false;
    }
    return true;
}

/*
    Helper function that populates a service \a interface descriptor
    with interface related attributes corresponding to the interface
    represented by \a interfaceID

    It is already assumed that a transaction has been started by the time
    this function is called.  This function will not rollback/commit the
    transaction.

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
*/
bool ServiceDatabase::populateInterfaceProperties(QServiceInterfaceDescriptor *interface, const QString &interfaceID)
{
    QSqlQuery query(QSqlDatabase::database(m_connectionName));
    QString statement("SELECT Key, Value FROM InterfaceProperty WHERE InterfaceID = ?");
    QList<QVariant> bindValues;
    bindValues.append(interfaceID);
    if (!executeQuery(&query, statement, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::populateInterfaceProperties():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    bool isFound = false;
    QString attribute;
    while (query.next()) {
        isFound = true;
        attribute = query.value(EBindIndex).toString();
        if (attribute == INTERFACE_CAPABILITY_KEY) {
            const QStringList capabilities = query.value(EBindIndex1).toString().split(",");
            if (capabilities.count() == 1 && capabilities[0].isEmpty()) {
                interface->d->attributes[QServiceInterfaceDescriptor::Capabilities]
                    = QStringList();
            } else {
                interface->d->attributes[QServiceInterfaceDescriptor::Capabilities]
                = capabilities;
            }
        } else if (attribute == INTERFACE_DESCRIPTION_KEY) {
            interface->d->attributes[QServiceInterfaceDescriptor::InterfaceDescription]
               = query.value(EBindIndex1).toString();
        } else if (attribute.startsWith("c_")) {
            interface->d->customAttributes[attribute.mid(2)]
               = query.value(EBindIndex1).toString();
        }
    }

    if (!isFound) {
        QString errorText("Database integrity corrupted, Properties for InterfaceID: %1 does not exist in the InterfaceProperty table for interface \"%2\"");
        m_lastError.setError(DBError::SqlError, errorText.arg(interfaceID).arg(interface->interfaceName()));
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::populateInterfaceProperties():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

/*
    Helper function that populates a service \a interface descriptor
    with service related attributes corresponding to the service
    represented by \a serviceID

    It is already assumed that a transaction has been started by the time
    this function is called.  This function will not rollback/commit the
    transaction.

    May set the last error to one of the following error codes:
    DBError::NoError
    DBError::SqlError
*/
bool ServiceDatabase::populateServiceProperties(QServiceInterfaceDescriptor *interface, const QString &serviceID)
{
    QSqlQuery query(QSqlDatabase::database(m_connectionName));
    QString statement("SELECT Key, Value FROM ServiceProperty WHERE ServiceID = ?");
    QList<QVariant> bindValues;
    bindValues.append(serviceID);
    if (!executeQuery(&query, statement, bindValues)) {
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::populateServiceProperties():-"
                    << qPrintable(m_lastError.text());
#endif
        return false;
    }

    bool isFound = false;
    QString attribute;
    while (query.next()) {
        isFound = true;
        attribute = query.value(EBindIndex).toString();
        if (attribute == SERVICE_DESCRIPTION_KEY) {
                interface->d->attributes[QServiceInterfaceDescriptor::ServiceDescription]
                    = query.value(EBindIndex1).toString();
        }
    }

    if (!isFound) {
        QString errorText("Database integrity corrupted, Service Properties for ServiceID: \"%1\" does not exist in the ServiceProperty table for service \"%2\"");
        m_lastError.setError(DBError::SqlError, errorText.arg(serviceID).arg(interface->serviceName()));
#ifdef QT_SFW_SERVICEDATABASE_DEBUG
        qWarning() << "ServiceDatabase::populateServiceProperties():-"
                    << "Problem:" << qPrintable(m_lastError.text());
#endif
        return false;
    }
    m_lastError.setError(DBError::NoError);
    return true;
}

#include "moc_servicedatabase_p.cpp"

QTM_END_NAMESPACE
