ormhandler.cpp 7.8 KB
/* ****************************************************************************
 * Copyright 2019 Open Systems Development BV                                 *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        *
 * DEALINGS IN THE SOFTWARE.                                                  *
 * ***************************************************************************/
#include "ormhandler.h"
#include "dcxmlconfig.h"
#include "log.h"
#include "dbrelation.h"
#include "threadcontext.h"

#include <QSqlRecord>
#include <QSqlField>
#include <QSqlRelation>
#include <QSqlIndex>

#include <QtDebug>

using namespace osdev::components;

typedef QList<DbRelation*>  Relations;

OrmHandler::OrmHandler(QObject *_parent)
    : QObject( _parent )
    , m_qhOrmTables()
    , m_dbConnection()
    , m_className( "OrmHandler" )
{
    m_dbConnection.setCredentials( DCXmlConfig::Instance().getDbCredentials() );

    // Check if a watchdog is required and connect those. If a database goes offline, all data should be redirected to the TransQueue.
    if( DCXmlConfig::Instance().getEnableDbWatchDog() )
    {
        // Start the watchdog and set the interval.
        m_dbConnection.startDbWatchDog( DCXmlConfig::Instance().getDbCheckInterval() );
    }
}

OrmHandler::~OrmHandler()
{
}

void OrmHandler::start()
{
    // connect the database.
    LogInfo( m_className, "Connecting to database.." );
    if( m_dbConnection.connectDatabase() )
    {
        // Read configuration
        QStringList tableList = DCXmlConfig::Instance().getOrmTables();

        if( 0 == tableList.count() )
        {
            tableList = m_dbConnection.getTableNames();
        }

        LogInfo( m_className,
                 QString("Connected to database %1") .arg( m_dbConnection.getDatabaseName() ) );

        // First make sure we have all tables created, before we create all relations...
        for( const QString& tableName : tableList )
        {
            createOrmObject( tableName );
        }
        // -------------------------------------------------
        // Create all relations on the same list of tables.
        for( const QString& tableName : tableList )
        {
            QString status;
            // If creation of the object succeeded it is safe to add the relations.
            switch( createOrmRelation( tableName ) )
            {
                case OrmStatus::OrmStatusFailed:
                    status = "failed.";
                    break;
                case OrmStatus::OrmStatusNoRelation:
                    status = "not created (As there are none.)";
                    break;
                case OrmStatus::OrmStatusSuccess:
                    status = "succeeded.";
                    break;
            }
            LogInfo( "[OrmHandler::start]", QString( "Creation of ORM Relation for table : %1 %2" ).arg( tableName ).arg( status ) );
        }
    }
    else
    {
        LogInfo( m_className,
                 QString( "Connection to database %1 failed with error : %2" )
                 .arg( m_dbConnection.getDatabaseName() )
                 .arg( m_dbConnection.getLastError() ) );

        //@todo : This will produce a SegmentationFault for now due to mutlihreaded pluginloading.
        //        This should be fixed in future releases by implementing a nice rolldown with locking
        //        mechanisms and shutdown events.
        QMetaObject::invokeMethod( qApp, "quit", Qt::QueuedConnection );
    }
}

bool OrmHandler::createOrmObject( const QString& _table )
{
    bool l_result = false;

    OrmTable *pModel = new OrmTable( m_dbConnection, this );
    if ( pModel )
    {
        pModel->setTrackField( DCXmlConfig::Instance().getRecordTrackFieldName() );
        pModel->setTable( m_dbConnection.quoteTableName( _table ) );
        m_qhOrmTables.insert( _table, pModel );

        // cascade connect the rejectedsignal.
        connect( pModel, &OrmTable::signalRejectedData, this, &OrmHandler::signalRejectedData );
        l_result = true;
    }

    return l_result;
}

OrmHandler::OrmStatus OrmHandler::createOrmRelation( const QString& _table )
{
    OrmStatus ormResult = OrmStatus::OrmStatusFailed;

    Relations lstRelations = m_dbConnection.getRelationByTableName( _table );
    if( lstRelations.isEmpty() )
    {
       return OrmStatus::OrmStatusNoRelation;
    }

    // Each relation found should be added to the table.
    for( auto& relation : lstRelations )
    {
        // Save the relation to the table administration.
        m_qhOrmTables.value( _table )->saveRelation( relation->constraintName(), relation );

        // Create a QSortProxyFilterModel and add to the table.
        QSortFilterProxyModel *pModel = new QSortFilterProxyModel();

        pModel->setSourceModel( m_qhOrmTables.value( relation->foreignTable() ) );
        m_qhOrmTables.value( _table )->setRelatedTable( relation->foreignTable(), pModel );
    }

    // Check if the number of relations found is equal to the number of relations
    // added to the ORM-table.
    if( lstRelations.count() == m_qhOrmTables.value( _table )->numberOfRelations() )
    {
        ormResult = OrmStatus::OrmStatusSuccess;
    }
    else
    {
        LogDebug( "[OrmHandler::createOrmRelation]", QString( "Number of relations : %1 => Number of relations registered : %2" ).arg( lstRelations.count() ).arg( m_qhOrmTables.value( _table )->numberOfRelations() ) );
        ormResult = OrmStatus::OrmStatusFailed;
    }

    return ormResult;
}

void OrmHandler::receiveData( const QSharedPointer<ORMRelData>& dataContainer )
{
    ThreadContextScope tcs( dataContainer->traceId() );
    LogDebug( "[OrmHandler::receiveData]", dataContainer->asString() );
    // Check if we have an actual database running. *if* it died, redirect immediately
    if( m_dbConnection.isOpen() )
    {
        if( m_qhOrmTables.contains( dataContainer->getMainTableName() ) )
        {
            LogDebug("[OrmHandler::receiveData]", QString("Table entry found for %1").arg( dataContainer->getMainTableName() ) );
            m_qhOrmTables.value( dataContainer->getMainTableName() )->writeData( dataContainer );
        }
        else
        {
            LogWarning("[OrmHandler::receiveData]", QString("No table entry found for %1").arg( dataContainer->getMainTableName() ) );
            emit signalRejectedData( dataContainer );
        }
    }
    else
    {
        emit signalRejectedData( dataContainer );
    }
}

QString OrmHandler::getPrimaryKey( const QString& tableName ) const
{
    if( m_qhOrmTables.contains( tableName ) )
    {
        return m_qhOrmTables.value( tableName )->primaryKey().name();
    }
    return QString();
}