/** * Handler for one indirect fire single object * $Id$ * $HeadURL$ * $Author$ */ #include "object_exercise.h" #include "convert_db_to_dds.h" #include "dds_handler.h" #include "exercise_state_machine.h" #include "object_exercise_phase.h" #include "handler_exercise.h" #include "settings.h" #include #include #include #include namespace ExerciseManagement { ObjectExercise::ObjectExercise(const std::shared_ptr &ddsHandler, const std::shared_ptr &db, const std::shared_ptr &exTblGen, HandlerExercise *exHandler) : QObject(exHandler) , m_ddsHandler(ddsHandler) , m_db(db) , m_exTblGen(exTblGen) , m_exerciseHandler(exHandler) , m_copyExercise(nullptr) , m_logger(getLogger("ObjectExercise")) { m_stateMachine = new ExerciseStateMachine(this); } ObjectExercise::~ObjectExercise() { delete m_copyExercise; delete m_stateMachine; } bool ObjectExercise::isValid() const { return m_data.id > 0; } bool ObjectExercise::isStopped() const { return ( isValid() && m_data.exerciseStateId == ExerciseManagement::ExerciseState::Stopped); } bool ObjectExercise::isRunning() const { return (m_data.exerciseStateId == ExerciseManagement::ExerciseState::Started); } Core::FunctionReplyObject ObjectExercise::createObject(const Database::ExmExercise &data, bool forceInsert) { m_data = data; fixupTimeZone(); if (m_data.id <= 0 || forceInsert) { m_data.exerciseStateId = ExerciseManagement::ExerciseState::Created; m_data.created = QDateTime::currentDateTimeUtc(); if (!m_db->insert(m_data)) { QString errText = m_db->getLastError().databaseText(); ERROR_LOG(m_logger) << "Error inserting exercise into database: " << errText; return Core::FunctionReplyObject(0, tr_lts("dds", "Error inserting ExmExercise-data into database: %1")).arg(errText); } // Id is serial -> will be generated by db => inside m_data after db-insert DEBUG_LOG(m_logger) << "New exerciseId: " << m_data.id; // Create exercise-schema if (!m_exTblGen->createExerciseSchema(m_data.id)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error creating database schema for exercise with id %1")).arg(m_data.id); // Create tables/views/indexes for each service if (!m_exTblGen->executeSql(m_data.id)) { // Remove already created schema & user m_exTblGen->dropExerciseSchema(m_data.id, false); return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error creating database tables/indexes/views for exercise with id %1")).arg(m_data.id); } } // Additional writing of sqlFiles if (m_logger.isDebugEnabled()) m_exTblGen->writeExerciseServicesSql(m_data.id); INFO_LOG(m_logger) << "Exercise created sucessfully."; // Publish Data via DTS onPublishData(); return Core::FunctionReplyObject(m_data.id); } void ObjectExercise::initStateMachine() { // Start ExerciseStateMachine m_stateMachine->init(); INFO_LOG(m_logger) << "StateMachine: currentState" << m_stateMachine->getCurrentState(); } Core::FunctionReplyObject ObjectExercise::updateObject(const Database::ExmExercise &data) { if ( !isValid() || !isEditable()) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Trying to update an already removed ExmExercise object")); m_data.id = data.id; m_data.name = data.name; m_data.beginTime = data.beginTime; m_data.endTime = data.endTime; m_data.startTime = data.startTime; m_data.stopTime = data.stopTime; m_data.trainingAreaId = data.trainingAreaId; m_data.radioGroupId = data.radioGroupId; m_data.activePhaseId = data.activePhaseId; m_data.symbolFillTypeId = data.symbolFillTypeId; m_data.forces = data.forces; m_data.description = data.description; m_data.defaultTimeZone = data.defaultTimeZone; m_data.linkedExerciseId = data.linkedExerciseId; m_data.sendToWms = data.sendToWms; m_data.transferred = data.transferred; m_data.hasHlaInterface = data.hasHlaInterface; m_data.operatorId = data.operatorId; m_data.workstationId = data.workstationId; fixupTimeZone(); INFO_LOG(m_logger) << "Trying to updateObject ExerciseData with id = " << data.id; if (!m_db->update(m_data)) { QString errText = m_db->getLastError().databaseText(); ERROR_LOG(m_logger) << "Failed to update ExerciseData with id = " << data.id << ": " << m_db->getLastError().databaseText(); return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error updating ExmExercise-data in database: %1")).arg(errText); } // Change state if different if (m_data.exerciseStateId != data.exerciseStateId) { Core::FunctionReplyObject retOb = switchToState(data.exerciseStateId, data.operatorId, data.workstationId); if (retOb.error()) return retOb; } INFO_LOG(m_logger) << "ExerciseData changed sucessfully."; onPublishData(); return Core::FunctionReplyObject(m_data.id); } Core::FunctionReplyObject ObjectExercise::removeObject(bool force) { if (isRunning() && !force) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Exercise still running")); // Change State to terminated if (!setExerciseState(ExerciseManagement::ExerciseState::Terminated)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "ExerciseState cannot be changed to terminated")); if (!m_exTblGen->dropExerciseSchema(m_data.id, false)) return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Exercise %1 (%2) can not be removed")).arg(m_data.name).arg(m_data.id); if (!m_db->removeExmExercise(m_data.id)) { QString errText = m_db->getLastError().databaseText(); ERROR_LOG(m_logger) << "Cannot remove exercise id = " << m_data.id << "from database: " << errText; return Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Exercise %1 (%2) can not be removed: %3")).arg(m_data.name).arg(m_data.id).arg(errText); } m_ddsHandler->dts()->removeExerciseByKey(m_data.id); sendStateToServiceManagement(m_data.exerciseStateId); // terminated INFO_LOG(m_logger) << "Exercise dropped sucessfully."; return Core::FunctionReplyObject(m_data.id); } bool ObjectExercise::setLinkedExerciseId(int newLinkedExerciseId) { m_data.linkedExerciseId = newLinkedExerciseId; if (!m_db->update(m_data)) { ERROR_LOG(m_logger) << "Failed to update ExerciseData with id = " << m_data.id << " : " << m_db->getLastError().text(); return false; } onPublishData(); return true; } bool ObjectExercise::setSendToWms(bool bSend) { m_data.sendToWms = bSend; if (!m_db->update(m_data)) { ERROR_LOG(m_logger) << "Failed to update ExerciseData with id = " << m_data.id << " : " << m_db->getLastError().text(); return false; } onPublishData(); return true; } bool ObjectExercise::setExerciseState(const ExerciseState &exerciseState) { if (m_data.exerciseStateId == exerciseState) return true; if (!isValid() || !isEditable()) { WARNING_LOG(m_logger) << "Trying to update an already removed ExmExercise object"; return false; } DEBUG_LOG(m_logger) << "## m_data.exerciseStateId = " << m_data.exerciseStateId; DEBUG_LOG(m_logger) << "## exerciseState = " << exerciseState; m_data.exerciseStateId = exerciseState; if (m_data.exerciseStateId == ExerciseManagement::ExerciseState::Stopped || m_data.exerciseStateId == ExerciseManagement::ExerciseState::Terminated || m_data.exerciseStateId == ExerciseManagement::ExerciseState::Unknown) m_data.radioGroupId.clear(); QDateTime changed = QDateTime::currentDateTimeUtc(); if (m_data.exerciseStateId == ExerciseState::Started) m_data.startTime = changed; if (m_data.exerciseStateId == ExerciseState::Stopped) m_data.stopTime = changed; if (!m_db->update(m_data)) { ERROR_LOG(m_logger) << "Error updating ExmExercise-data in database (exerciseState) for exercise id = " << m_data.id << " : " << m_db->getLastError().text();; return false; } Database::ExmExerciseStateChange stateChange; stateChange.exerciseId = m_data.id; stateChange.exerciseStateId = m_data.exerciseStateId; stateChange.operatorId = m_data.operatorId; stateChange.workstationId = m_data.workstationId; stateChange.changedAt = changed.currentMSecsSinceEpoch(); // record exerciseStateChange if (!m_db->insert(stateChange)) WARNING_LOG(m_logger) << "Recording change of exerciseState failed: " << m_db->getLastError().text();; // Publish Data via DTS onPublishData(); return true; } void ObjectExercise::onPublishData() { INFO_LOG(m_logger) << "Publish ExmExercise-data to dds (" << m_data.id << ", " << m_data.name << ") ..."; if (!m_ddsHandler->dts()->publish(convert(m_data))) ERROR_LOG(m_logger) << "Error publishing ExmExercise-data to dds"; } void ObjectExercise::fixupTimeZone() { if (m_data.defaultTimeZone.isEmpty()) m_data.defaultTimeZone = QLatin1Char('Z'); // zulu const char tz = m_data.defaultTimeZone.at(0).toLatin1(); if (tz < 'A' || tz == 'J' || tz > 'Z') m_data.defaultTimeZone = QLatin1Char('Z'); // zulu } Core::FunctionReplyObject ObjectExercise::switchToState(const ExerciseState &newExerciseState, int operatorId, int workstationId) { Core::FunctionReplyObject ret; INFO_LOG(m_logger) << "-------------- switchToState: " << newExerciseState << ", currentState = " << m_stateMachine->getCurrentState(); m_data.operatorId = operatorId; m_data.workstationId = workstationId; bool ok = m_stateMachine->switchToState(newExerciseState); if (!ok) { ret = Core::FunctionReplyObject(m_data.id, tr_lts("dds", "Error switching to state %1")).arg(newExerciseState.underlying()); } NOTICE_LOG(m_logger) << "-------------- after switch: currentState" << m_stateMachine->getCurrentState(); NOTICE_LOG(m_logger) << "-> OperatorId: " << m_data.operatorId << ", workstationId: " << m_data.workstationId; return ret; } static int s_requestId = 0; bool ObjectExercise::sendStateToServiceManagement(ExerciseState state) { Core::ServiceGroupType sgt = Core::ServiceGroupType::Core; // invalid here switch (state.underlying()) { case ExerciseState::Created: sgt = Core::ServiceGroupType::ExercisePreparation; break; case ExerciseState::Started: sgt = Core::ServiceGroupType::ExerciseExecution; break; case ExerciseState::Paused: sgt = Core::ServiceGroupType::ExercisePaused; break; case ExerciseState::Stopped: sgt = Core::ServiceGroupType::ExerciseAfterActionReview; break; case ExerciseState::Terminated: sgt = Core::ServiceGroupType::Master; // Master + ExerciseId != 0 -> stopp all groups for this exercise break; case ExerciseState::Unknown: break; } if (sgt == Core::ServiceGroupType::Core) return false; if (sgt != Core::ServiceGroupType::Master) DEBUG_LOG(m_logger) << "Ex " << m_data.id << " - Trying to enable service group " << sgt; else DEBUG_LOG(m_logger) << "Ex " << m_data.id << " - Trying to stop all services"; // if there is an old group, stop them ServiceManagement::GroupRequest request(sgt, m_ddsHandler->ddsConnector()->getDomainId(), m_data.id, 0, ++s_requestId, {}); #ifndef EXM_OBJECT_EXERCISE_NO_HANDLER if (!m_exerciseHandler || !m_exerciseHandler->startGroupRequest(request)) { ERROR_LOG(m_logger) << "Ex " << m_data.id << " - Failed to start group " << sgt; return false; } #endif if (sgt != Core::ServiceGroupType::Master) DEBUG_LOG(m_logger) << "Ex " << m_data.id << " - Sent start group " << sgt; else DEBUG_LOG(m_logger) << "Ex " << m_data.id << " - Sent stop all services"; return true; } bool ObjectExercise::transferExerciseToEvaluationDb() { if (m_data.transferred) return true; if (!m_copyExercise) m_copyExercise = new DBAdministration::CopyExercise(m_logger, DBAdministration::CopyExercise::CopyForEvaluation, this); connect(m_copyExercise, &DBAdministration::CopyExercise::copyDbFinished, this, &ObjectExercise::onTransferDbFinished); connect(m_copyExercise, &DBAdministration::CopyExercise::copyDbFailed, this, &ObjectExercise::onErrorOccured); if (!m_copyExercise->init(m_data.id, Settings::serverDatabaseHost(), Settings::serverDatabaseName(), Settings::evaluationDatabaseHost(), Settings::evaluationDatabaseName()) || !m_copyExercise->doCopy()) { WARNING_LOG(m_logger) << "Could not backup exercise database to evaluation server"; return false; } NOTICE_LOG(m_logger) << "Ex " << m_data.id << " - Started copying exercise from " << Settings::serverDatabaseHost() << ":" << Settings::serverDatabaseName() << " to " << Settings::evaluationDatabaseHost() << ":" << Settings::evaluationDatabaseName(); return true; } void ObjectExercise::onTransferDbFinished() { NOTICE_LOG(m_logger) << "Ex " << m_data.id << " - Finished copying exercise from " << Settings::serverDatabaseHost() << ":" << Settings::serverDatabaseName() << " to " << Settings::evaluationDatabaseHost() << ":" << Settings::evaluationDatabaseName(); m_data.transferred = true; if (!m_db->update(m_data)) ERROR_LOG(m_logger) << "Error updating ExmExercise-data (transferred) in database (exerciseState) for exercise id = " << m_data.id << " : " << m_db->getLastError().text();; delete m_copyExercise; m_copyExercise = nullptr; //publish because we changed transferred onPublishData(); } void ObjectExercise::onErrorOccured() { WARNING_LOG(m_logger) << "Ex " << m_data.id << " - Copying exercise from " << Settings::serverDatabaseHost() << ":" << Settings::serverDatabaseName() << " to " << Settings::evaluationDatabaseHost() << ":" << Settings::evaluationDatabaseName(); delete m_copyExercise; m_copyExercise = nullptr; } } // Namespace ExerciseManagement