Files
qt-migration-legatus/qt6-after/qt6-migrated-legatus/Lts/Services/WarehouseGuez/WsBaseDataInterface/data_manager.cpp
T
2026-10-03 09:20:13 +02:00

562 lines
19 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @RDE_HEADER@
* The DataManager class takes strucure element data from the HandlerStructureElement,
* offers an interface to get and set structure data
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-12-05
*/
#include "WsBaseDataInterface/data_manager.h"
#include <utility>
#include <WMS_Interface/IWMS2ECC.h>
#include <WMS_Interface/wms_ecc_equipmenttypes.h>
#include <WMS_Interface/wms_ecc_registration.h>
#include <ngltools/ngltools_filesystem.h>
#include <ngldb/nsqlquery.h>
#include <CodeGen/SqlGenerator.h>
#include "main_handler.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QtCore/QMap>
#include <QSqlRecord>
#include <QtCore/QTimer>
#include <QtCore/QVector>
namespace WarehouseGuez
{
namespace WsBaseDataInterface
{
static constexpr int s_exerciseOffset = 1000000;
static inline QString tlnTable(int exerciseId)
{
return QStringLiteral("tln_") + QString::number(exerciseId);
}
static inline QString tlnZuordnungTable(int exerciseId)
{
return QStringLiteral("tln_zuordnung_") + QString::number(exerciseId);
}
static inline QString triggerName(int exerciseId)
{
if (exerciseId <= 0)
return QStringLiteral("whg_notifyInsertParticipantAssignment");
return QStringLiteral("whg_notifyInsertParticipantAssignment_") + QString::number(exerciseId);
}
//---------------------------------------------------------------------------------------------------------
DataManager::DataManager(MainHandler *mainHandler)
: m_mainHandler(mainHandler)
, m_webSocketServer(nullptr)
, m_logger(getLogger(QStringLiteral("DataManager")))
{
}
//---------------------------------------------------------------------------------------------------------
bool DataManager::init()
{
reconnectSignals();
return true;
}
//---------------------------------------------------------------------------------------------------------
LtsWebSocket::ReplyMessage *DataManager::equipmentTypes(const QString &uuid) const
{
using namespace Messages;
IWMS2ECC::equipTypeMap etMap;
auto cbEqTypeWh = [&etMap](const WarehouseGuez::Database::EquipmentTypeWh &data)
{
IWMS2ECC::EquipmentType et;
et.id = data.id;
et.name = data.name;
et.minEquipNo = data.ausstgNrMin;
et.maxEquipNo = data.ausstgNrMax;
et.specialType = 0; // not used in GUEZ
et.taVulnerabilityModelId = data.vbAgdus;
etMap.insert(et.id, et);
};
m_mainHandler->db()->select(cbEqTypeWh);
EquipmentTypes eTypes;
eTypes.setEquipmentTypes(etMap);
QJsonObject obj = eTypes.toJsonAll();
INFO_LOG(m_logger) << "equipTypeMap.size: " << etMap.size() << ", " << QJsonDocument(obj).toJson();
return new EquipmentTypesMessage(obj, uuid);
}
void DataManager::newExerciseInformationsFromEXCON(const EXCON::Exercises &ex)
{
const IWMS2ECC::exerciseMap exMap = ex.getExercises();
DEBUG_LOG(m_logger) << "Exercises map.size: " << exMap.size();
QVector<Database::Exercise> dataVec;
m_mainHandler->db()->select(dataVec);
for (const IWMS2ECC::Exercise &exercise : exMap)
{
WarehouseGuez::Database::Exercise ex;
ex.id = exercise.id;
ex.name = exercise.name;
ex.beginn = QDateTime::fromMSecsSinceEpoch(exercise.start);
ex.aktUebung = exercise.actExercise;
ex.tln = tlnTable(exercise.id);
ex.tlnZuordnung = tlnZuordnungTable(exercise.id);
bool ok;
if (findExercise(exercise.id, dataVec))
ok = m_mainHandler->db()->update(ex);
else
ok = m_mainHandler->db()->insert(ex);
if (!ok)
{
ERROR_LOG(m_logger) << "Error while insert/update in exercise";
break;
}
}
}
bool DataManager::findExercise(int exerciseId, const QVector<Database::Exercise> &dataVec)
{
for (const Database::Exercise &exercise : dataVec)
{
if (exercise.id == exerciseId)
return true;
}
return false;
}
static inline QString fixupString(const QString &in)
{
// Warehouse in GUEZ loves SQL-Injection
QString ret;
ret.reserve(in.size());
for (const QChar &c : in)
{
if (c == QLatin1Char('\"') ||
c == QLatin1Char('\'') ||
c.unicode() == 0x60 || // GRAVE ACCENT - `
c.unicode() == 0xb4 || // ACUTE ACCENT - ´
c == QLatin1Char(';'))
continue;
ret += c;
}
return ret;
}
static inline void bindPlayer(NSqlQuery &q, const IWMS2ECC::PlayerInfo &pl)
{
q.bindValue(QStringLiteral(":id"), pl.playerId);
q.bindValue(QStringLiteral(":hierarchie"), fixupString(pl.hierarchy));
q.bindValue(QStringLiteral(":typ_id"), pl.typeId);
q.bindValue(QStringLiteral(":ausstg_typ_id"), pl.equipTypeId);
q.bindValue(QStringLiteral(":ausstg_nr"), pl.equipNo);
q.bindValue(QStringLiteral(":zuordnung"), pl.assignState);
q.bindValue(QStringLiteral(":y_nr"), fixupString(pl.alias));
q.bindValue(QStringLiteral(":info"), fixupString(pl.info));
q.bindValue(QStringLiteral(":loeschkennung"), pl.isDeleted ? 1 : 0); // bool -> smallint :(
q.bindValue(QStringLiteral(":zeit"), NSqlQuery::qDateTimeToQVariant(QDateTime::currentDateTime()));
}
void DataManager::participantsFromEXCON(const PlayerInfo &pl)
{
const QMap<int,IWMS2ECC::PlayerInfo> plMap = pl.getAllPlayerInfo();
if (plMap.isEmpty())
{
DEBUG_LOG(m_logger) << "No valid exercise -> no participants";
return;
}
// first get exerciseId
int exerciseId = plMap.firstKey() / s_exerciseOffset;
DEBUG_LOG(m_logger) << "ExerciseId: " << exerciseId;
DEBUG_LOG(m_logger) << "plMap.size(): " << plMap.size();
// table is created at exercise creation
QString tlnOut = tlnTable(exerciseId);
NSqlQuery q(m_mainHandler->db()->database());
// If entries exist delete them
QString stmt = QStringLiteral("DELETE FROM ") + tlnOut;
DEBUG_LOG(m_logger) << "Executing delete statement: " << stmt;
q.exec(stmt);
stmt = QStringLiteral("INSERT INTO ") + tlnOut + QStringLiteral("( id, hierarchie, typ_id, ausstg_typ_id, ausstg_nr, zuordnung, y_nr, info, loeschkennung, zeit ) VALUES (" )
+ QStringLiteral(":id, :hierarchie, :typ_id, :ausstg_typ_id, :ausstg_nr, :zuordnung, :y_nr, :info, :loeschkennung, :zeit )");
DEBUG_LOG(m_logger) << "Preparing insert statement: " << stmt;
q.prepare(stmt);
for (const IWMS2ECC::PlayerInfo &pl : plMap)
{
bindPlayer(q, pl);
DEBUG_LOG(m_logger) << "Executing insert statement for participant: " << pl.playerId;
if (!q.exec())
ERROR_LOG(m_logger) << "Error inserting data for participantId: " << pl.playerId;
}
}
bool DataManager::participantFromEXCON(const IWMS2ECC::PlayerInfo &pl, int modType)
{
DEBUG_LOG(m_logger) << "IN participantFromEXCON: modType: " << modType << ", participantId: " << pl.playerId;
DataModification modificationType = static_cast<DataModification>(modType);
int exerciseId = pl.playerId / s_exerciseOffset;
QString tlnOut = tlnTable(exerciseId);
NSqlQuery q(m_mainHandler->db()->database());
DEBUG_LOG(m_logger) << "Executing delete statement: " << QStringLiteral("DELETE FROM %1 WHERE id = %2").arg(tlnOut).arg(pl.playerId);
q.exec(QStringLiteral("DELETE FROM %1 WHERE id = %2").arg(tlnOut).arg(pl.playerId));
switch(modificationType)
{
case DataModification::eModified:
case DataModification::eAdded:
{
QString stmt = QStringLiteral("INSERT INTO ") + tlnOut +
QStringLiteral("( id, hierarchie, typ_id, ausstg_typ_id, ausstg_nr, zuordnung, y_nr, info, loeschkennung, zeit ) VALUES (" ) +
QStringLiteral(":id, :hierarchie, :typ_id, :ausstg_typ_id, :ausstg_nr, :zuordnung, :y_nr, :info, :loeschkennung, :zeit )");
if (!q.prepare(stmt))
{
ERROR_LOG(m_logger) << "Error while preparing insert stmt: " << stmt;
return false;
}
bindPlayer(q, pl);
DEBUG_LOG(m_logger) << "Executing insert statement: " << stmt;
if (!q.exec())
{
ERROR_LOG(m_logger) << "Error inserting data for participantId: " << pl.playerId;
return false;
}
break;
}
case DataModification::eRemoved:
break;
} // end switch
return true;
}
bool DataManager::exerciseChanged(const IWMS2ECC::Exercise &data, DataModification modType)
{
DEBUG_LOG(m_logger) << "IN exerciseChanged: id = " << data.id << ", actExercise: " << data.actExercise;
Database::Exercise exData;
exData.id = data.id;
exData.name = data.name;
exData.beginn = QDateTime::fromMSecsSinceEpoch(data.start);
exData.tln = tlnTable(data.id);
exData.tlnZuordnung = tlnZuordnungTable(data.id);
exData.aktUebung = data.actExercise;
switch(modType)
{
case DataModification::eAdded:
{
Database::Exercise tmp;
if (m_mainHandler->db()->selectExercise(exData.id, tmp))
{
if (!m_mainHandler->db()->update(exData))
{
ERROR_LOG(m_logger) << "Error while updating exerciseData for exercise: " << data.id;
return false;
}
}
else
{
if (!m_mainHandler->db()->insert(exData))
{
ERROR_LOG(m_logger) << "Error while inserting exerciseData for exercise: " << data.id;
return false;
}
createTableTlnOut(exData.id);
createTableTlnZuordnung(exData.id);
}
return true;
}
case DataModification::eModified:
{
if (!m_mainHandler->db()->update(exData))
{
ERROR_LOG(m_logger) << "Error while updating exerciseData for exercise: " << data.id;
return false;
}
return true;
}
case DataModification::eRemoved:
{
if (!m_mainHandler->db()->removeExercise(data.id))
{
ERROR_LOG(m_logger) << "Error while removing exerciseData for exercise: " << data.id;
return false;
}
return true;
}
}
ERROR_LOG(m_logger) << "DataModification Type: " << modType << " not handled in switch";
return false;
}
void DataManager::createTableTlnOut(int exerciseId)
{
ERROR_LOG(m_logger) << "createTableTlnOut";
// xmlPath
const QString xmlPath = NGL::directory(NGL::FileType::ConfigFile, QLatin1String("xml"));
QString xmlFile = xmlPath + QStringLiteral("/whg.xml");
CodeGen::SqlGenerator sqlGen(xmlFile);
QString tlnOut = tlnTable(exerciseId);
ERROR_LOG(m_logger) << "tlnOut: " << tlnOut;
sqlGen.createStatementsForTable(QStringLiteral("tln"), tlnOut);
const CodeGen::SqlGenerator::SqlStatements stmts = sqlGen.getStatements(CodeGen::Global);
QSqlRecord record = m_mainHandler->db()->database().record(tlnOut);
NSqlQuery q(m_mainHandler->db()->database());
for (const QString &stmt : stmts.createTables)
{
ERROR_LOG(m_logger) << "stmt: " << stmt;
if (record.isEmpty())
{
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error executing stmt: " << stmt;
}
}
}
void DataManager::createTableTlnZuordnung(int exerciseId)
{
ERROR_LOG(m_logger) << "in createTableTlnZuordnung";
// xmlPath
const QString xmlPath = NGL::directory(NGL::FileType::ConfigFile, QLatin1String("xml"));
QString xmlFile = xmlPath + QStringLiteral("/whg.xml");
CodeGen::SqlGenerator sqlGen(xmlFile);
const QString tlnZuordnung = tlnZuordnungTable(exerciseId);
ERROR_LOG(m_logger) << "tlnZuordnung: " << tlnZuordnung;
sqlGen.createStatementsForTable(QStringLiteral("tln_zuordnung"), tlnZuordnung);
const CodeGen::SqlGenerator::SqlStatements stmts = sqlGen.getStatements(CodeGen::Global);
QSqlRecord record = m_mainHandler->db()->database().record(tlnZuordnung);
NSqlQuery q(m_mainHandler->db()->database());
for (const QString &stmt : stmts.createTables)
{
DEBUG_LOG(m_logger) << "stmt: " << stmt;
if (record.isEmpty())
{
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error executing stmt: " << stmt;
}
}
for (const QString &stmt : stmts.createSequences)
{
DEBUG_LOG(m_logger) << "stmt: " << stmt;
if (record.isEmpty())
{
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error executing stmt: " << stmt;
}
}
createTriggerForExercise(q, exerciseId);
}
void DataManager::createTriggerForExercise(NSqlQuery &q, int exerciseId)
{
QString stmt;
const QString triggerNameExercise(triggerName(exerciseId));
const QString tlnZuordnungTableExercise(tlnZuordnungTable(exerciseId));
// drop possible old trigger
stmt = QLatin1String("DROP TRIGGER IF EXISTS ") + triggerNameExercise +
QLatin1String(" ON ") + tlnZuordnungTableExercise;
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error dropping possible old trigger: " << stmt;
// Create trigger
stmt = QLatin1String("CREATE OR REPLACE FUNCTION public.") + triggerNameExercise +
QLatin1String("() RETURNS TRIGGER AS $$ "
" BEGIN "
" PERFORM pg_notify('") + triggerNameExercise + QLatin1String("', CAST(NEW.id AS text));"
" RETURN NEW;"
"END;"
"$$ LANGUAGE plpgsql;");
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error while creating function in statement: " << stmt;
stmt = QLatin1String("CREATE TRIGGER ") + triggerNameExercise +
QLatin1String(" AFTER INSERT ON ") + tlnZuordnungTableExercise +
QLatin1String(" FOR EACH ROW EXECUTE PROCEDURE public.") + triggerNameExercise + QLatin1String("();");
if (!q.exec(stmt))
ERROR_LOG(m_logger) << "Error while creating trigger in statement: " << stmt;
QSqlDriver *driver = m_mainHandler->db()->database().driver();
// create subscription
connect(driver, QOverload<const QString &, QSqlDriver::NotificationSource, const QVariant &>::of(&QSqlDriver::notification),
this, &DataManager::onNotification);
if (!driver->subscribeToNotification(triggerNameExercise))
WARNING_LOG(m_logger) << "Error subscribing to DB notification " << triggerNameExercise;
else
DEBUG_LOG(m_logger) << "Sucessfully subscribed to DB notification " << triggerNameExercise;
}
void DataManager::onInsertParticipantAssignment(int participantId, int exerciseId)
{
INFO_LOG(m_logger) << "Reading ParticipantAssignment from database!";
NSqlQuery q(m_mainHandler->db()->database());
QString stmt = QLatin1String("SELECT id, ausstg_nr, zuordnung, y_nr, info FROM ") + m_mainHandler->db()->schemaName() +
QLatin1Char('.') + tlnZuordnungTable(exerciseId) +
QLatin1String(" WHERE gesendet = false");
if (participantId > 0)
stmt += QLatin1String(" AND id = ") + QString::number(participantId);
stmt += QLatin1String(" ORDER BY zeit");
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Error selecting data for participantId: " << participantId << ", exerciseId: " << exerciseId;
return;
}
// we may get back more than one row, esp. in case participantId == 0
while (q.next())
{
// use the 'correct' participant id in case participantId == 0 was given (happens during startup)
participantId = q.value(0).toInt() - (exerciseId * s_exerciseOffset);
DEBUG_LOG(m_logger) << "participantId: " << participantId;
Registration reg;
reg.setPlayerId(participantId);
reg.setAssignment(IWMS2ECC::int2PlayerAssignState(q.value(2).toInt()));
if (!q.value(1).isNull())
reg.setEquipNo(q.value(1).toInt());
if (!q.value(3).isNull())
reg.setAlias(q.value(3).toString());
if (!q.value(4).isNull())
reg.setInfo(q.value(4).toString());
reg.setExerciseId(exerciseId);
reg.setOk(true);
Messages::ParticipantRegisterInExconRequest *msg = new Messages::ParticipantRegisterInExconRequest(reg.toJson(),
QLatin1String("blub"));
INFO_LOG(m_logger) << "registration: " << msg->toJsonString();
m_webSocketServer->sendToAllClients(msg);
}
}
void DataManager::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
{
Q_UNUSED(source);
// search coresponding signal
INFO_LOG(m_logger) << "IN onNotification name: " << name;
const QString triggerNameEx0 = triggerName(0);
if (name.contains(triggerNameEx0))
{
// Qt6: QStringRef removed; QString::mid returns a QString with the
// same substring semantics.
QString name_ = name.mid(triggerNameEx0.size() + 1);
int exerciseId = name_.toInt();
int participantId = payload.toInt();
INFO_LOG(m_logger) << "exerciseId: " << exerciseId << ", participantId: " << participantId;
onInsertParticipantAssignment(participantId, exerciseId);
}
}
void DataManager::reconnectSignals()
{
NSqlQuery q(m_mainHandler->db()->database());
QVector<Database::Exercise> vecExercises;
m_mainHandler->db()->select(vecExercises);
for (const auto &exercise : std::as_const(vecExercises))
{
createTriggerForExercise(q, exercise.id);
const QString tn = triggerName(exercise.id);
QTimer::singleShot(5000, this, [this, tn]() { onNotification(tn, QSqlDriver::UnknownSource, QLatin1String("0")); });
}
}
void DataManager::registerReply(int exerciseId, qint32 participantId, int returnValue)
{
DEBUG_LOG(m_logger) << "registerReply for exercise id = " << exerciseId << ", participant id = " << participantId << ", return value = " << returnValue;
NSqlQuery q(m_mainHandler->db()->database());
qint32 newParticipantId = participantId + (exerciseId * s_exerciseOffset);
QString stmt = QLatin1String("UPDATE ") + m_mainHandler->db()->schemaName() +
QLatin1Char('.') + tlnZuordnungTable(exerciseId) +
QLatin1String(" SET gesendet = true, error_code = ") + QString::number(returnValue) +
QLatin1String(" WHERE id = ") + QString::number(newParticipantId) +
QLatin1String(" AND gesendet = false");
DEBUG_LOG(m_logger) << "registerReply: stmt = " << stmt;
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Cannot update reply entry for participant: " << participantId
<< " in exercise: " << exerciseId << ", returnValue: " << returnValue;
}
else
{
INFO_LOG(m_logger) << "Update successful for participant " << participantId << " in exercise: " << exerciseId;
}
if (returnValue != 0)
{
QString stmt1 = QLatin1String("UPDATE ") + m_mainHandler->db()->schemaName() +
QLatin1String(".") + tlnTable(exerciseId) +
QLatin1String(" SET ausstg_nr = -1, zeit = ") + QString::number(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()) +
QLatin1String(" WHERE id = ") + QString::number(newParticipantId);
DEBUG_LOG(m_logger) << "registerReply: stmt1 = " << stmt1;
if (!q.exec(stmt1))
{
ERROR_LOG(m_logger) << "Cannot update austtg_nr for participant: " << participantId
<< " in exercise: " << exerciseId << ", returnValue: " << returnValue;
}
else
{
INFO_LOG(m_logger) << "Update ausstg_nr successful for participant " << participantId << " in exercise: " << exerciseId;
}
}
}
} // namespace WsBaseDataInterface
} // namespace WarehouseGuez