Files
qt5-legatus-legacy/Lts/Services/OperatorManagement/handler_locked_topics.cpp
T
2026-08-13 18:33:10 +02:00

427 lines
15 KiB
C++
Executable File

/**
* Block/unblock topics
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2018-01-15
*/
#include "handler_locked_topics.h"
#include "main_handler.h"
#include "handler_operator.h"
#include <DDS/FTS/generic_fts_impl.h>
#include <DDS/DTS/generic_dts_impl.h>
#include <DDS/Core/function_reply_object.h>
#include <QMutableHashIterator>
namespace OperatorManagement
{
HandlerLockedTopics::HandlerLockedTopics(MainHandler &mainHandler,
QObject *parent)
: QObject(parent)
, m_mainHandler(mainHandler)
, m_logger(getLogger("HandlerLockedTopics"))
{}
bool HandlerLockedTopics::init()
{
m_db = m_mainHandler.db();
auto ddsConnector = m_mainHandler.dds()->ddsConnector();
if (!m_db || !ddsConnector)
return false;
ddsConnector->initSubscriber();
{
m_ftsTryLockEntity.reset(ddsConnector->createFTS<FTS::TryLockEntity>());
const auto cb = [this](const rti::core::SampleIdentity & cookie, const Core::TryLockEntityRequest & request)
{
this->onTryLockEntityRequest(cookie, request);
};
m_ftsTryLockEntity->setSubscriberCallback(cb);
}
{
m_ftsTryUnlockEntity.reset(ddsConnector->createFTS<FTS::TryUnlockEntity>());
const auto cb = [this](const rti::core::SampleIdentity & cookie, const Core::TryUnlockEntityRequest & request)
{
this->onTryUnlockEntityRequest(cookie, request);
};
m_ftsTryUnlockEntity->setSubscriberCallback(cb);
}
auto cbDatabase = [this](const Database::OpmLockedTopics & data)
{
DEBUG_LOG(m_logger) << "Locked topic from db: " << data.exerciseId.toInt() << ", " << data.workstationId
<< ", " << data.operatorId << data.entityTypeName;
m_lockedTopics.insert(Core::key(data.entityId, data.exerciseId.isNull() ? 0 : data.exerciseId.toInt(), data.entityTypeName), data);
};
m_db->selectByLocked(cbDatabase, true);
m_dtsLockedEntity.reset(ddsConnector->createDTS<DTS::LockedEntityImpl>());
return true;
}
static bool needsUnlock(int operatorId, int workstationId, const Database::OpmLockedTopics &data)
{
// same operator -> check if other workstation
if (operatorId == data.operatorId)
{
return true; // NT, 18.09.20 remove. From former (crashed) session, independent from workstation
// if (workstationId == 0) // logout
// return true;
// if (workstationId != data.workstationId) // logged in on another workstation
// return true;
// return false;
}
// other operator on a workstation previously locked by another operator -> unlock
if (workstationId != 0 && workstationId == data.workstationId)
return true;
return false;
}
void HandlerLockedTopics::operatorLoggedInOut(int operatorId, int workstationId)
{
// check if there are stale locks (e.g. by crashing client) - then remove all of them - if there are any
DEBUG_LOG(m_logger) << "operatorLoggedInOut: " << workstationId << ", " << operatorId;
// Ueberall wurden - ohne erase wohlgemerkt - mutable Iteratoren verwendet. Nur hier - wo erase verwendet wurde - nicht! :')
QMutableHashIterator<Core::key, Database::OpmLockedTopics> it(m_lockedTopics);
while (it.hasNext())
{
const auto lockedTopic = it.next().value();
DEBUG_LOG(m_logger) << "check if unlock: id=" << lockedTopic.id
<< ", exId=" << lockedTopic.exerciseId.toInt()
<< ", wsId=" << lockedTopic.workstationId
<< ", opId=" << lockedTopic.operatorId;
if (!needsUnlock(operatorId, workstationId, lockedTopic))
{
continue;
}
DEBUG_LOG(m_logger) << "Unlocking topic: id=" << lockedTopic.id
<< ", exId=" << lockedTopic.exerciseId.toInt()
<< ", wsId=" << lockedTopic.workstationId
<< ", opId=" << lockedTopic.operatorId;
unlockInDb(lockedTopic);
removeLockedEntity(lockedTopic.id);
it.remove();
}
}
void HandlerLockedTopics::onTryLockEntityRequest(const rti::core::SampleIdentity &cookie,
const Core::TryLockEntityRequest &request)
{
if (m_logger.isDebugEnabled())
dumpLockedTopics(__LINE__);
auto ret = tryLock(request);
if (!ret.errorString().empty())
{
WARNING_LOG(m_logger) << Core::FunctionReplyObject(ret.FunctionReply::id(), ret.errorString(), ret.errorParams()).translated() << " - " << request;
}
else if (ret.alreadyLocked())
{
WARNING_LOG(m_logger) << "onTryLockEntityRequest failed because the topic was already locked by ws " << ret.alreadyLocked().get().workstationName() << " and op " << ret.alreadyLocked().get().operatorName() << " - " << request;
}
m_ftsTryLockEntity->sendReply(cookie, ret);
if (m_logger.isDebugEnabled())
dumpLockedTopics(__LINE__);
}
Core::TryLockEntityReply lockErrorReply(const long topicId, const std::string &msg)
{
return Core::TryLockEntityReply(-1, msg, { std::to_string(topicId) }, {}, {});
}
Core::TryLockEntityReply lockAlreadyLockedReply(const std::string &ws, const QString &op)
{
return Core::TryLockEntityReply(-1, {}, {},{}, Core::LockEntityError(ws, op.toStdString()));
}
Core::TryLockEntityReply lockSuccessReply(const Database::OpmLockedTopics &dbStruct)
{
return Core::TryLockEntityReply(dbStruct.id, {}, {}, {}, {});
}
Core::TryLockEntityReply HandlerLockedTopics::tryLock(const Core::TryLockEntityRequest &request)
{
DEBUG_LOG(m_logger) << "tryLock : " << request;
const auto topicId = request.entityId();
const Core::key k(request.entityId(), request.exerciseId(), QString::fromStdString(request.entityTypeName()));
Database::OpmLockedTopics dbStruct(toDb(request));
auto it = m_lockedTopics.constFind(k);
if (it == m_lockedTopics.constEnd())
{
// noch nicht vorhanden -> add
dbStruct.timeStampLock = QDateTime::currentMSecsSinceEpoch();
if (!m_db->insert(dbStruct))
{
return lockErrorReply(topicId, tr_lts("dds", "Failed to lock element with id %1 in database"));
}
m_lockedTopics.insert(k, dbStruct);
Core::LockedEntity lckEntity(request.operatorId(), request.workstationId(),
request.exerciseId(), request.entityId(),
request.entityTypeName(), dbStruct.id);
publishLockedEntity(lckEntity);
return lockSuccessReply(dbStruct);
}
// NOTE: don't check if there is already a lock for that exact same key (same op, ws, topic, ...): Reason: Szenario: in 2 sitmaps "Exercise Purpose" aufschalten... 2. Dlg muss gelockt werden !!!
// already locked by another user
const Database::OpmLockedTopics &data = *it;
const auto opName = m_mainHandler.getHandlerOperator()->operatorNameById(data.operatorId);
const auto wsName = m_mainHandler.dds()->dtcAsm()->asset(data.workstationId).name();
return lockAlreadyLockedReply(wsName, opName);
}
//Core::TryLockEntityReply HandlerLockedTopics::tryLockModify(const Core::TryLockEntityRequest &request)
//{
// DEBUG_LOG(m_logger) << "tryLockModify: " << request;
// const auto lockEntityId = request.id();
// // requestedKey == new key
// const Core::key requestedKey(request.entityId(), request.exerciseId(), QString::fromStdString(request.entityTypeName()));
// // is requested topic already locked?
// {
// auto it = m_lockedTopics.constFind(requestedKey);
// if (it != m_lockedTopics.constEnd())
// {
// // vorhanden -> locked by another user/dlg
// // or be me??? (locked by this dialog instance / topicId ?
// const Database::OpmLockedTopics &data = it.value();
// if (lockEntityId != data.id)
// {
// // vorhanden and locked by another user/dlg:
// // "release" my *former* lock:
// removeReleaseOutdatedLock(lockEntityId);
// const auto opName = m_mainHandler->getHandlerOperator()->operatorNameById(data.operatorId);
// const auto wsName = m_mainHandler->dds()->dtcAsm()->asset(data.workstationId).name();
// return lockAlreadyLockedReply(wsName, opName);
// }
// }
// }
// // not already lock -> 'update' the entry:
// //
// // 1) DB-Update
// //
// Database::OpmLockedTopics dbStruct(toDb(request));
// dbStruct.id = lockEntityId; // to enable dbUpdate :-)
// dbStruct.timeStampLock = QDateTime::currentMSecsSinceEpoch();
// if (!m_db->update(dbStruct))
// {
// return lockErrorReply(lockEntityId, tr_lts("dds", "Failed to update lock entity (%1) in database"));
// }
// //
// // 2) DDS-Update
// //
// Core::LockedEntity lckEntity(request.operatorId(), request.workstationId(),
// request.exerciseId(), request.entityId(),
// request.entityTypeName(), dbStruct.id);
// publishLockedEntity(lckEntity);
// //
// // 3) QHash-Update
// //
// // update both: key and value!!! by removing the key-value-pair with topicId ...
// {
// auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(),
// [&lockEntityId](const Database::OpmLockedTopics &lockedTopic)
// {
// return (lockEntityId == lockedTopic.id);
// });
// if (lockedTopicIt == m_lockedTopics.constEnd())
// {
// return lockErrorReply(lockEntityId, tr_lts("dds", "Failed to update lock entity (%1) on the heap"));
// }
// const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt;
// DEBUG_LOG(m_logger) << LOGFUNCTION << ": locked topic id " << lockedTopic.id;
// // ... and then reinserting the updated lock entity (as the id has now changed) - while the key (op, ws, exc, topic id) stays the same!
// m_lockedTopics[requestedKey] = dbStruct;
// }
// return lockSuccessReply(dbStruct); // success
//}
//bool HandlerLockedTopics::removeReleaseOutdatedLock(const int lockEntityId)
//{
// DEBUG_LOG(m_logger) << "removeReleaseOutdatedLock: lockEntityId = " << lockEntityId;
// auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(),
// [&lockEntityId](const Database::OpmLockedTopics &lockedTopic)
// {
// return (lockEntityId == lockedTopic.id);
// });
// if (lockedTopicIt == m_lockedTopics.constEnd())
// {
// ERROR_LOG(m_logger) << LOGFUNCTION << ": Failed to release outdated lock as lock entity id ("<<lockedTopicIt<<") was not found!";
// Q_ASSERT(false);
// return false;
// }
// const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt;
// DEBUG_LOG(m_logger) << LOGFUNCTION << ": found lock entity " << lockEntityId;
// unlockInDb(lockedTopic);
// removeLockedEntity(lockEntityId);
// m_lockedTopics.erase(lockedTopicIt);
// return true;
//}
void HandlerLockedTopics::onTryUnlockEntityRequest(const rti::core::SampleIdentity &cookie,
const Core::TryUnlockEntityRequest &request)
{
if (m_logger.isDebugEnabled())
dumpLockedTopics(__LINE__);
auto ret = tryUnlock(request);
if (!ret.errorString().empty())
{
WARNING_LOG(m_logger) << Core::FunctionReplyObject(ret.FunctionReply::id(), ret.errorString(), ret.errorParams()).translated() << " - " << request;
}
m_ftsTryUnlockEntity->sendReply(cookie, ret);
if (m_logger.isDebugEnabled())
dumpLockedTopics(__LINE__);
}
Core::FunctionReply HandlerLockedTopics::tryUnlock(const Core::TryUnlockEntityRequest &request)
{
DEBUG_LOG(m_logger) << "tryUnlock: " << request;
const auto lockEntityId = request.id();
auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(),
[lockEntityId](const Database::OpmLockedTopics &lockedTopic)
{
return (lockEntityId == lockedTopic.id);
});
if (lockedTopicIt == m_lockedTopics.constEnd())
{
// nicht vorhanden => unlock nicht möglich
return Core::FunctionReply(-1, tr_lts("dds", "Failed to unlock unknown lock with id %1."), {}, { std::to_string(lockEntityId) });
}
// found => unlock
const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt;
unlockInDb(lockedTopic);
removeLockedEntity(lockedTopic.id);
m_lockedTopics.erase(lockedTopicIt);
return Core::FunctionReply(); // success!
}
void HandlerLockedTopics::unlockInDb(const Database::OpmLockedTopics &data)
{
Database::OpmLockedTopics dbStruct(data);
dbStruct.locked = false;
dbStruct.timeStampUnlock = QDateTime::currentMSecsSinceEpoch();
m_db->update(dbStruct);
}
Database::OpmLockedTopics HandlerLockedTopics::toDb(const Core::LockedEntityBase &request)
{
Database::OpmLockedTopics ret;
ret.locked = true;
ret.entityId = request.entityId();
ret.entityTypeName = QString::fromStdString(request.entityTypeName());
ret.operatorId = request.operatorId();
ret.workstationId = request.workstationId();
ret.exerciseId = request.exerciseId() ? QVariant(request.exerciseId()) : QVariant();
return ret;
}
bool HandlerLockedTopics::publishLockedEntity(const Core::LockedEntity &entity)
{
if (m_dtsLockedEntity)
{
m_dtsLockedEntity->write(entity);
return true;
}
ERROR_LOG(m_logger) << "Publishing LockedEntity " << entity.id() << " failed: DTS not available!";
return false;
}
bool HandlerLockedTopics::removeLockedEntity(int64_t id)
{
Core::LockedEntity entity;
entity.id(id);
if (m_dtsLockedEntity)
{
m_dtsLockedEntity->remove(entity);
return true;
}
ERROR_LOG(m_logger) << "Removing LockedEntity " << entity.id() << " failed: DTS not available!";
return false;
}
//Core::key HandlerLockedTopics::topicIdToKey(long id)
//{
// QHashIterator<Core::key, Database::OpmLockedTopics> it (m_lockedTopics);
// while (it.hasNext())
// {
// it.next();
// const Database::OpmLockedTopics &topicRef = it.value();
// if (topicRef.id == id)
// {
// return it.key();
// }
// } // while (it.hasNext())
// return {};
//}
void HandlerLockedTopics::dumpLockedTopics(int line)
{
QHashIterator<Core::key, Database::OpmLockedTopics> it (m_lockedTopics);
INFO_LOG(m_logger) << " dump m_lockedTopics, START size= " << m_lockedTopics.size()
<< " (callerline=" << line << ")";
while (it.hasNext())
{
it.next();
const Core::key &keyRef = it.key();
const Database::OpmLockedTopics &valRef = it.value();
INFO_LOG(m_logger) << " entityTypeName=" << keyRef.entityTypeName << "'"
<< ", entityId=" << keyRef.entityId
<< ", exerciseId=" << keyRef.exerciseId
<< " - eTN=" << valRef.entityTypeName
<< ", entityId=" << valRef.entityId
<< ", exId=" << valRef.exerciseId.toInt()
<< ", opId=" << valRef.operatorId;
}
INFO_LOG(m_logger) << " dump m_lockedTopics, END size= " << m_lockedTopics.size();
}
} // namespace