/****************************************************************************
**
** https://www.qxorm.com/
** Copyright (C) 2013 XDL Team (ic-east.com)
**
** This file is part of the QxOrm library
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any
** damages arising from the use of this software
**
** Commercial Usage
** Licensees holding valid commercial QxOrm licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and XDL Team
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file 'license.gpl3.txt' included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met : http://www.gnu.org/copyleft/gpl.html
**
** If you are unsure which license is appropriate for your use, or
** if you have questions regarding the use of this file, please contact :
** ic-east.com
**
****************************************************************************/

#include <QxPrecompiled.h>

#include <QxRestApi/QxRestApi.h>

#include <QxDao/IxPersistable.h>
#include <QxDao/IxPersistableCollection.h>
#include <QxDao/IxPersistableList.h>
#include <QxDao/QxSqlError.h>
#include <QxDao/QxSqlQuery.h>
#include <QxDao/QxSqlDatabase.h>
#include <QxDao/QxSqlSaveMode.h>

#include <QxFactory/QxFactoryX.h>

#include <QxRegister/IxClass.h>
#include <QxRegister/QxClassX.h>

#include <QxValidator/QxInvalidValueX.h>

#ifndef _QX_NO_JSON
#include <QxSerialize/QJson/QxSerializeQJson_QxSqlQuery.h>
#include <QxSerialize/QJson/QxSerializeQJson_QStringList.h>
#include <QxSerialize/QJson/QxSerializeQJson_IxSqlElement.h>
#include <QxSerialize/QJson/QxSerializeQJson_QxCollection.h>
#include <QxSerialize/QJson/QxSerializeQJson_QxInvalidValue.h>
#include <QxSerialize/QJson/QxSerializeQJson_QxInvalidValueX.h>
#endif // _QX_NO_JSON

#include <QxCommon/QxException.h>
#include <QxCommon/QxExceptionCode.h>

#include <QxMemLeak/mem_leak.h>

namespace qx
{

   struct Q_DECL_HIDDEN QxRestApi::QxRestApiImpl
   {

      QString m_requestId;                         //!< Request identifier (GUID for example)
      QString m_entity;                            //!< Entity where to process request
      QString m_action;                            //!< Action to execute (count, fetch_by_id, fetch_all, fetch_by_query, insert, update, etc...)
      QString m_function;                          //!< Entity static function to call with action 'call_entity_function' and signature : static QJsonValue myEntity::myFct(const QJsonValue & request)
      QStringList m_columns;                       //!< Columns to fetch
      QStringList m_relations;                     //!< Relations to fetch
      QStringList m_outputFormat;                  //!< JSON output format (same syntax as relations)
      QString m_database;                          //!< Database to use to process request
      QString m_query;                             //!< SQL query to execute
      QString m_data;                              //!< Data used to process request
      QSqlError m_error;                           //!< Error after executing the request
      qx::IxPersistable_ptr m_instance;            //!< Current instance to execute request
      QSqlDatabase m_db;                           //!< Current database to execute request
      qx_query m_qxQuery;                          //!< Query used by some actions
      long m_countResult;                          //!< Result after a count query
      qx_bool m_existResult;                       //!< Result after a exist query
      QxInvalidValueX m_validateResult;            //!< Result after a validate query
      qx::dao::save_mode::e_save_mode m_eSaveMode; //!< Save mode for 'save' action
      bool m_bUseExecBatch;                        //!< If true then use the QSqlQuery::execBatch() method to improve performance inserting/updating/deleting a list of instances to database (but doesn't fill the last inserted identifier in the C++ instances)

#ifndef _QX_NO_JSON

      QJsonValue m_requestJson;  //!< Request which contains all parameters (as JSON format)
      QJsonValue m_responseJson; //!< Response after executing the request (as JSON format)
      QJsonValue m_dataJson;     //!< Data used to process request (as JSON format)
      QJsonValue m_errorJson;    //!< Error after executing the request (as JSON format)

#endif // _QX_NO_JSON

      QxRestApiImpl() : m_countResult(0), m_eSaveMode(qx::dao::save_mode::e_none), m_bUseExecBatch(false)
      {
         ;
      }
      ~QxRestApiImpl() { ; }

#ifndef _QX_NO_JSON

      void clear();
      void resetRequest();
      QJsonValue processRequestAsArray(const QJsonValue &request);
      void buildError(int errCode, const QString &errDesc);
      void buildError(const QSqlError &error);
      bool parseRequest(const QString &request);
      bool createInstance();
      bool decodeRequest();
      bool checkRequest();
      bool executeAction();
      bool buildResponse();
      bool formatResponse();
      bool doRequest();
      void getMetaData();
      QJsonValue getMetaData(IxClass *pClass);
      qx_bool callEntityFunction();
      void getDatabases();

#endif // _QX_NO_JSON
   };

   QxRestApi::QxRestApi(QObject *parent /* = NULL */) : QObject(parent), m_pImpl(new QxRestApiImpl()) { ; }

   QxRestApi::~QxRestApi() { ; }

   void QxRestApi::clearAll() { (*m_pImpl) = QxRestApiImpl(); }

   QString QxRestApi::getErrorDesc() const { return (m_pImpl->m_error.isValid() ? (m_pImpl->m_error.driverText() + "\n" + m_pImpl->m_error.databaseText()) : QString()); }

#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
   int QxRestApi::getErrorCode() const
   {
      return (m_pImpl->m_error.isValid() ? m_pImpl->m_error.nativeErrorCode().toInt() : 0);
   }
#else  // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
   int QxRestApi::getErrorCode() const
   {
      return (m_pImpl->m_error.isValid() ? m_pImpl->m_error.number() : 0);
   }
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))

   QSqlError QxRestApi::getError() const
   {
      return m_pImpl->m_error;
   }

#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
   QString QxRestApi::getNativeErrorCode() const
   {
      return m_pImpl->m_error.nativeErrorCode();
   }
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))

   void QxRestApi::setEntity(const QString &entity)
   {
      m_pImpl->m_entity = entity;
   }

   void QxRestApi::setAction(const QString &action) { m_pImpl->m_action = action; }

   void QxRestApi::setFunction(const QString &fct) { m_pImpl->m_function = fct; }

   void QxRestApi::setColumns(const QStringList &columns) { m_pImpl->m_columns = columns; }

   void QxRestApi::setRelations(const QStringList &relations) { m_pImpl->m_relations = relations; }

   void QxRestApi::setOutputFormat(const QStringList &outputFormat) { m_pImpl->m_outputFormat = outputFormat; }

   void QxRestApi::setDatabase(const QString &database) { m_pImpl->m_database = database; }

   void QxRestApi::setQuery(const QString &query) { m_pImpl->m_query = query; }

   void QxRestApi::setData(const QString &data) { m_pImpl->m_data = data; }

   void QxRestApi::setUseExecBatch(bool useExecBatch) { m_pImpl->m_bUseExecBatch = useExecBatch; }

   QString QxRestApi::processRequest(const QString &request)
   {
#ifdef _QX_NO_JSON
      QString msg = "QxOrm library must be built without _QX_NO_JSON compilation option to be able to call REST API module";
      m_pImpl->m_error = QSqlError(msg, "Cannot process request with _QX_NO_JSON compilation option", QSqlError::UnknownError);
      return "{ \"error\": \"" + msg + "\" }";
#else  // _QX_NO_JSON
      m_pImpl->clear();
      if (m_pImpl->parseRequest(request))
      {
         processRequest(m_pImpl->m_requestJson);
      }
      QJsonValue result = (m_pImpl->m_errorJson.isNull() ? m_pImpl->m_responseJson : m_pImpl->m_errorJson);
      return qx::cvt::to_string(result);
#endif // _QX_NO_JSON
   }

#ifndef _QX_NO_JSON

   void QxRestApi::setData(const QJsonValue &data)
   {
      m_pImpl->m_dataJson = data;
   }

   QJsonValue QxRestApi::processRequest(const QJsonValue &request)
   {
      m_pImpl->clear();

#ifdef _QX_ENABLE_MONGODB
      if (qx::QxSqlDatabase::getSingleton()->getDriverName() == "QXMONGODB")
      {
         m_pImpl->m_db = QSqlDatabase();
      }
      else
#endif // _QX_ENABLE_MONGODB
      {
         m_pImpl->m_db = qx::QxSqlDatabase::getDatabase(m_pImpl->m_error);
      }

      if (m_pImpl->m_error.isValid())
      {
         m_pImpl->buildError(m_pImpl->m_error);
         return m_pImpl->m_errorJson;
      }
      if (request.isArray())
      {
         return m_pImpl->processRequestAsArray(request);
      }
      m_pImpl->m_requestJson = request;
      if (!m_pImpl->doRequest())
      {
         return m_pImpl->m_errorJson;
      }
      return m_pImpl->m_responseJson;
   }

   QJsonValue QxRestApi::QxRestApiImpl::processRequestAsArray(const QJsonValue &request)
   {
      QJsonArray responseArray;
      QJsonArray requestArray = request.toArray();
      if (requestArray.count() <= 0)
      {
         buildError(9999, "Request array is empty");
         return m_errorJson;
      }

      bool bTransaction = false;
      if (m_db.driver() && m_db.driver()->hasFeature(QSqlDriver::Transactions))
      {
         bTransaction = m_db.transaction();
      }

      for (int i = 0; i < requestArray.count(); i++)
      {
         resetRequest();
         m_requestJson = requestArray.at(i);
         if (!doRequest())
         {
            break;
         }
         responseArray.append(m_responseJson);
      }

      bool bTransactionOk = true;
      if (!m_errorJson.isNull() && bTransaction)
      {
         bTransactionOk = m_db.rollback();
      }
      else if (bTransaction)
      {
         bTransactionOk = m_db.commit();
      }
      if (m_errorJson.isNull() && !bTransactionOk)
      {
         buildError(m_db.lastError());
      }

      if (!m_errorJson.isNull())
      {
         return m_errorJson;
      }
      m_responseJson = responseArray;
      return m_responseJson;
   }

   bool QxRestApi::QxRestApiImpl::doRequest()
   {
      if (!decodeRequest())
      {
         return false;
      }
      if (!checkRequest())
      {
         return false;
      }
      if (!createInstance())
      {
         return false;
      }
      if (!executeAction())
      {
         return false;
      }
      if (!buildResponse())
      {
         return false;
      }
      if (!formatResponse())
      {
         return false;
      }
      return true;
   }

   void QxRestApi::QxRestApiImpl::clear()
   {
      // Reset error and instance
      m_responseJson = QJsonValue();
      m_errorJson = QJsonValue();
      m_error = QSqlError();
      m_instance.reset();
      m_db = QSqlDatabase();
   }

   void QxRestApi::QxRestApiImpl::resetRequest()
   {
      m_requestId = "";
      m_action = "";
      m_entity = "";
      m_data = "";
      m_query = "";
      m_function = "";
      m_database = "";
      m_qxQuery = qx_query();
      m_dataJson = QJsonValue();
      m_requestJson = QJsonValue();
      m_responseJson = QJsonValue();
      m_eSaveMode = qx::dao::save_mode::e_none;
      m_bUseExecBatch = false;
      m_columns.clear();
      m_relations.clear();
      m_outputFormat.clear();
      m_instance.reset();
   }

   void QxRestApi::QxRestApiImpl::buildError(int errCode, const QString &errDesc)
   {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      m_error = QSqlError(errDesc, "", QSqlError::UnknownError, QString::number(errCode));
#else  // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      m_error = QSqlError(errDesc, "", QSqlError::UnknownError, errCode);
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      QJsonObject errJson;
      QJsonObject errDetail;
      errDetail.insert("code", errCode);
      errDetail.insert("desc", errDesc);
      errJson.insert("error", errDetail);
      if (!m_requestId.isEmpty())
      {
         errJson.insert("request_id", m_requestId);
      }
      m_errorJson = errJson;
   }

   void QxRestApi::QxRestApiImpl::buildError(const QSqlError &error)
   {
      if (!error.isValid())
      {
         return;
      }
      m_error = error;
      QJsonObject errJson;
      QJsonObject errDetail;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      errDetail.insert("code", error.nativeErrorCode());
#else  // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      errDetail.insert("code", error.number());
#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
      QString errMsg = (error.driverText() + "\n" + error.databaseText());
      errDetail.insert("desc", errMsg);
      errJson.insert("error", errDetail);
      if (!m_requestId.isEmpty())
      {
         errJson.insert("request_id", m_requestId);
      }
      m_errorJson = errJson;
   }

   bool QxRestApi::QxRestApiImpl::createInstance()
   {
      // Some actions doesn't require any instance
      if (m_action == "get_meta_data")
      {
         return true;
      }
      if (m_action == "get_databases")
      {
         return true;
      }
      if (m_action == "call_custom_query")
      {
         return true;
      }
      if (m_action == "call_entity_function")
      {
         return true;
      }

      // Check if entity implements qx::IxPersistable interface
      m_instance.reset();
      if (m_entity.isEmpty())
      {
         buildError(9999, "JSON request is invalid : 'entity' field is empty");
         return false;
      }
      if (!qx::QxClassX::implementIxPersistable(m_entity))
      {
         buildError(9999, "Entity '" + m_entity + "' must implement qx::IxPersistable interface");
         return false;
      }
      m_instance = qx::IxPersistable_ptr(static_cast<qx::IxPersistable *>(qx::create_void_ptr(m_entity)));
      if (!m_instance)
      {
         buildError(9999, "Entity '" + m_entity + "' is not valid : unable to create a new qx::IxPersistable instance");
         return false;
      }

      // Check if action requires some input data
      if (m_action == "count")
      {
         return true;
      }
      if (m_action == "delete_all")
      {
         return true;
      }
      if (m_action == "destroy_all")
      {
         return true;
      }
      if (m_action == "delete_by_query")
      {
         return true;
      }
      if (m_action == "destroy_by_query")
      {
         return true;
      }

      // Check if some input data has been provided by caller
      if (m_dataJson.isNull() && (m_action != "fetch_all") && (m_action != "fetch_by_query"))
      {
         buildError(9999, "No data provided for entity '" + m_entity + "'");
         return false;
      }

      // Check data format : single instance, or array of instances, or collection of instances (hash-map key/value)
      if (m_dataJson.isArray())
      {
         std::shared_ptr<qx::IxPersistableCollection> lst;
         QJsonArray dataArray = m_dataJson.toArray();
         if (dataArray.count() <= 0)
         {
            buildError(9999, "Data array is empty for entity '" + m_entity + "'");
            return false;
         }
         QJsonValue dataFirst = dataArray.first();
         if (dataFirst.isNull())
         {
            buildError(9999, "First item in data array is null for entity '" + m_entity + "'");
            return false;
         }
         if (!dataFirst.isObject())
         {
            buildError(9999, "First item in data array is not an object for entity '" + m_entity + "'");
            return false;
         }
         QJsonObject dataFirstObject = dataFirst.toObject();
         if (dataFirstObject.isEmpty())
         {
            buildError(9999, "First object in data array is empty for entity '" + m_entity + "'");
            return false;
         }
         if ((dataFirstObject.count() == 2) && dataFirstObject.contains("key") && dataFirstObject.contains("value"))
         {
            lst = m_instance->qxNewPersistableCollection(false);
         }
         else
         {
            lst = m_instance->qxNewPersistableCollection(true);
         }
         m_instance = std::static_pointer_cast<qx::IxPersistable>(lst);
      }
      else if ((m_action == "fetch_all") || (m_action == "fetch_by_query"))
      {
         std::shared_ptr<qx::IxPersistableCollection> lst;
         lst = m_instance->qxNewPersistableCollection(true);
         m_instance = std::static_pointer_cast<qx::IxPersistable>(lst);
      }

      // Fill data in qx::IxPersistable instance
      qx_bool bFromJson = (m_dataJson.isNull() ? qx_bool(true) : m_instance->fromJson_(m_dataJson));
      if (!bFromJson)
      {
         buildError(static_cast<int>(bFromJson.getCode()), bFromJson.getDesc());
         return false;
      }
      return true;
   }

   bool QxRestApi::QxRestApiImpl::parseRequest(const QString &request)
   {
      // Parse request as JSON format
      QJsonParseError jsonError;
      QByteArray requestAsByteArray = request.toUtf8();
      QJsonDocument doc = QJsonDocument::fromJson(requestAsByteArray, (&jsonError));
      if (jsonError.error != QJsonParseError::NoError)
      {
         buildError(static_cast<int>(jsonError.error), "Error parsing JSON request : " + jsonError.errorString());
         return false;
      }

      // Request can be an array or a single object
      m_requestJson = (doc.isArray() ? QJsonValue(doc.array()) : QJsonValue(doc.object()));
      return true;
   }

   bool QxRestApi::QxRestApiImpl::decodeRequest()
   {
      // Check if request is a valid JSON object
      if (m_requestJson.isNull())
      {
         buildError(9999, "Request is NULL");
         return false;
      }
      if (!m_requestJson.isObject())
      {
         buildError(9999, "Request is not a JSON object");
         return false;
      }
      QJsonObject request = m_requestJson.toObject();

      // Extract request identifier
      if (request.contains("request_id"))
      {
         m_requestId = request.value("request_id").toString();
      }

      // Extract action
      if (request.contains("action"))
      {
         m_action = request.value("action").toString();
      }
      else
      {
         buildError(9999, "Parameter 'action' is required and cannot be empty");
         return false;
      }
      if (m_action.isEmpty())
      {
         buildError(9999, "Parameter 'action' cannot be empty");
         return false;
      }

      // Extract entity
      if (request.contains("entity"))
      {
         m_entity = request.value("entity").toString();
      }

      // Extract data
      if (request.contains("data"))
      {
         m_dataJson = request.value("data");
      }
      else if (!m_data.isEmpty())
      {
         qx_bool bParseOk = qx::cvt::from_string(m_data, m_dataJson);
         if (!bParseOk)
         {
            buildError(static_cast<int>(bParseOk.getCode()), bParseOk.getDesc());
            return false;
         }
      }

      // Extract function
      if (request.contains("fct"))
      {
         qx::cvt::from_json(request.value("fct"), m_function);
      }

      // Extract columns
      if (request.contains("columns"))
      {
         qx::cvt::from_json(request.value("columns"), m_columns);
      }

      // Extract relations
      if (request.contains("relations"))
      {
         qx::cvt::from_json(request.value("relations"), m_relations);
      }

      // Extract output format
      if (request.contains("output_format"))
      {
         qx::cvt::from_json(request.value("output_format"), m_outputFormat);
      }

      // Extract database key
      if (request.contains("database"))
      {
         m_database = request.value("database").toString();
      }

      // Extract use exec batch setting
      if (request.contains("use_exec_batch"))
      {
         m_bUseExecBatch = (request.value("use_exec_batch").toBool() || (request.value("use_exec_batch").toString() == "1"));
      }

      // Extract save mode for 'save' action
      if (request.contains("save_mode"))
      {
         QString sSaveMode = request.value("save_mode").toString();
         bool bSaveModeAsInt = false;
         int iSaveMode = sSaveMode.toInt(&bSaveModeAsInt);
         if (bSaveModeAsInt)
         {
            m_eSaveMode = static_cast<qx::dao::save_mode::e_save_mode>(iSaveMode);
         }
         else
         {
            m_eSaveMode = ((sSaveMode == "check_insert_or_update") ? qx::dao::save_mode::e_check_insert_or_update : ((sSaveMode == "insert_only") ? qx::dao::save_mode::e_insert_only : ((sSaveMode == "update_only") ? qx::dao::save_mode::e_update_only : qx::dao::save_mode::e_none)));
         }
      }

      // Extract query
      if (request.contains("query"))
      {
         qx::cvt::from_json(request.value("query"), m_qxQuery);
      }
      else if (!m_query.isEmpty())
      {
         m_qxQuery = qx_query(m_query);
      }

      return true;
   }

   bool QxRestApi::QxRestApiImpl::checkRequest()
   {
      bool isEntityRequired = ((m_action == "get_meta_data") || (m_action == "call_entity_function"));
      isEntityRequired = (isEntityRequired || (m_action == "fetch_by_id") || (m_action == "fetch_all") || (m_action == "fetch_by_query"));
      isEntityRequired = (isEntityRequired || (m_action == "insert") || (m_action == "update") || (m_action == "save"));
      isEntityRequired = (isEntityRequired || (m_action == "delete_by_id") || (m_action == "delete_all") || (m_action == "delete_by_query"));
      isEntityRequired = (isEntityRequired || (m_action == "destroy_by_id") || (m_action == "destroy_all") || (m_action == "destroy_by_query"));
      isEntityRequired = (isEntityRequired || (m_action == "exec_custom_query") || (m_action == "exist") || (m_action == "validate") || (m_action == "count"));
      if (isEntityRequired && (m_entity.isEmpty()))
      {
         buildError(9999, "Parameter 'entity' is required and cannot be empty for action '" + m_action + "'");
         return false;
      }

      bool isDataRequired = ((m_action == "fetch_by_id") || (m_action == "delete_by_id") || (m_action == "destroy_by_id"));
      isDataRequired = (isDataRequired || (m_action == "insert") || (m_action == "update") || (m_action == "save"));
      isDataRequired = (isDataRequired || (m_action == "exist") || (m_action == "validate"));
      if (isDataRequired && (m_dataJson.isNull()))
      {
         buildError(9999, "Parameter 'data' is required and cannot be empty for action '" + m_action + "'");
         return false;
      }

      bool isQueryRequired = ((m_action == "fetch_by_query") || (m_action == "delete_by_query") || (m_action == "destroy_by_query"));
      isQueryRequired = (isQueryRequired || (m_action == "exec_custom_query") || (m_action == "call_custom_query"));
      if (isQueryRequired && (m_qxQuery.query().isEmpty()))
      {
         buildError(9999, "Parameter 'query' is required and cannot be empty for action '" + m_action + "'");
         return false;
      }

      bool isFunctionRequired = (m_action == "call_entity_function");
      if (isFunctionRequired && (m_function.isEmpty()))
      {
         buildError(9999, "Parameter 'fct' is required and cannot be empty for action '" + m_action + "'");
         return false;
      }

      return true;
   }

   bool QxRestApi::QxRestApiImpl::executeAction()
   {
      m_error = QSqlError();
      m_errorJson = QJsonValue();

      try
      {
         if (m_action == "count")
         {
            m_countResult = 0;
            m_error = m_instance->qxCount(m_countResult, m_qxQuery, (&m_db), m_relations);
         }
         else if (m_action == "fetch_by_id")
         {
            QVariant id;
            m_error = m_instance->qxFetchById(id, m_columns, m_relations, (&m_db));
         }
         else if (m_action == "fetch_all")
         {
            m_error = m_instance->qxFetchAll(NULL, m_columns, m_relations, (&m_db));
         }
         else if (m_action == "fetch_by_query")
         {
            m_error = m_instance->qxFetchByQuery(m_qxQuery, NULL, m_columns, m_relations, (&m_db));
         }
         else if (m_action == "insert")
         {
            m_error = m_instance->qxInsert(m_relations, (&m_db), m_bUseExecBatch);
         }
         else if (m_action == "update")
         {
            m_error = m_instance->qxUpdate(m_qxQuery, m_columns, m_relations, (&m_db), m_bUseExecBatch);
         }
         else if (m_action == "save")
         {
            m_error = m_instance->qxSave(m_relations, (&m_db), m_eSaveMode);
         }
         else if (m_action == "delete_by_id")
         {
            QVariant id;
            m_error = m_instance->qxDeleteById(id, (&m_db), m_bUseExecBatch);
         }
         else if (m_action == "delete_all")
         {
            m_error = m_instance->qxDeleteAll(&m_db);
         }
         else if (m_action == "delete_by_query")
         {
            m_error = m_instance->qxDeleteByQuery(m_qxQuery, (&m_db));
         }
         else if (m_action == "destroy_by_id")
         {
            QVariant id;
            m_error = m_instance->qxDestroyById(id, (&m_db), m_bUseExecBatch);
         }
         else if (m_action == "destroy_all")
         {
            m_error = m_instance->qxDestroyAll(&m_db);
         }
         else if (m_action == "destroy_by_query")
         {
            m_error = m_instance->qxDestroyByQuery(m_qxQuery, (&m_db));
         }
         else if (m_action == "exec_custom_query")
         {
            m_error = m_instance->qxExecuteQuery(m_qxQuery, NULL, (&m_db));
         }
         else if (m_action == "exist")
         {
            QVariant id;
            m_existResult = m_instance->qxExist(id, (&m_db));
         }
         else if (m_action == "validate")
         {
            m_validateResult = m_instance->qxValidate();
         }
         else if (m_action == "call_custom_query")
         {
            m_error = qx::dao::call_query(m_qxQuery, (&m_db));
         }
         else if (m_action == "call_entity_function")
         {
            if (!callEntityFunction())
            {
               return false;
            }
         }
         else if (m_action == "get_meta_data")
         {
            getMetaData();
         }
         else if (m_action == "get_databases")
         {
            getDatabases();
         }
         else
         {
            buildError(9999, "Unknown action '" + m_action + "'");
            return false;
         }
      }
      catch (const qx::exception &x)
      {
         buildError(9999, "An exception occurred executing action '" + m_action + "' : " + QString::number(x.getCode()) + " - " + x.getDescription());
         return false;
      }
      catch (const std::exception &e)
      {
         buildError(9999, "An exception occurred executing action '" + m_action + "' : " + QString(e.what()));
         return false;
      }
      catch (...)
      {
         buildError(9999, "Unknown exception occurred executing action '" + m_action + "'");
         return false;
      }

      if (m_error.isValid())
      {
         buildError(m_error);
      }
      return (!m_error.isValid());
   }

   bool QxRestApi::QxRestApiImpl::buildResponse()
   {
      if (m_action == "get_meta_data")
      {
         return m_errorJson.isNull();
      }
      if (m_action == "get_databases")
      {
         return m_errorJson.isNull();
      }

      QJsonObject response;
      if (m_action == "count")
      {
         response.insert("count", static_cast<double>(m_countResult));
         m_responseJson = response;
         return true;
      }
      else if (m_action == "exist")
      {
         response.insert("exist", (m_existResult ? true : false));
         m_responseJson = response;
         return true;
      }
      else if (m_action == "validate")
      {
         response.insert("invalid_values", qx::cvt::to_json(m_validateResult));
         m_responseJson = response;
         return true;
      }
      else if (m_action == "call_custom_query")
      {
         response.insert("query_output", qx::cvt::to_json(m_qxQuery));
         m_responseJson = response;
         return true;
      }
      else if ((m_action == "delete_all") || (m_action == "delete_by_query"))
      {
         response.insert("deleted", true);
         m_responseJson = response;
         return true;
      }
      else if ((m_action == "destroy_all") || (m_action == "destroy_by_query"))
      {
         response.insert("destroyed", true);
         m_responseJson = response;
         return true;
      }
      else if (m_action == "call_entity_function")
      {
         return true;
      }

      QString outputFormat = (m_outputFormat.isEmpty() ? QString() : QString("filter: " + m_outputFormat.join(" | ")));
      if (outputFormat.isEmpty() && ((m_action == "insert") || (m_action == "update") || (m_action == "save")))
      {
         outputFormat = QX_JSON_SERIALIZE_ONLY_ID;
      }
      else if (outputFormat.isEmpty() && ((m_action == "delete_by_id") || (m_action == "destroy_by_id")))
      {
         outputFormat = QX_JSON_SERIALIZE_ONLY_ID;
      }
      m_responseJson = m_instance->toJson_(outputFormat);
      return true;
   }

   bool QxRestApi::QxRestApiImpl::formatResponse()
   {
      QJsonObject response;
      if (!m_requestId.isEmpty())
      {
         response.insert("request_id", m_requestId);
      }
      response.insert("data", m_responseJson);
      m_responseJson = response;
      return true;
   }

   qx_bool QxRestApi::QxRestApiImpl::callEntityFunction()
   {
      qx::any anyResponse;
      std::vector<qx::any> anyRequest;
      anyRequest.push_back(m_dataJson);
      if (m_entity.isEmpty())
      {
         buildError(9999, "Unable to call entity function : 'entity' field is empty");
         return qx_bool(false);
      }
      if (m_function.isEmpty())
      {
         buildError(9999, "Unable to call entity function : 'fct' field is empty");
         return qx_bool(false);
      }
      if (!qx::QxClassX::getFctStatic(m_entity, m_function, true))
      {
         buildError(9999, "Unable to call entity function : '" + m_entity + "::" + m_function + "' function not found (or not registered in QxOrm context)");
         return qx_bool(false);
      }
      qx_bool bInvokeFct = qx::QxClassX::invokeStatic(m_entity, m_function, anyRequest, (&anyResponse));
      if (bInvokeFct)
      {
         m_responseJson = qx::any_cast<QJsonValue>(anyResponse);
      }
      else
      {
         buildError(static_cast<int>(bInvokeFct.getCode()), bInvokeFct.getDesc());
      }
      return bInvokeFct;
   }

   void QxRestApi::QxRestApiImpl::getMetaData()
   {
      QJsonObject response;
      QxClassX::registerAllClasses();
      if (m_entity == "*")
      {
         QJsonArray entities;
         QxCollection<QString, IxClass *> *pAllClasses = QxClassX::getAllClasses();
         if (!pAllClasses)
         {
            buildError(9999, "Unable to access to registered classes");
            return;
         }
         for (auto itr = pAllClasses->begin(); itr != pAllClasses->end(); ++itr)
         {
            IxClass *pClass = itr->second;
            if (!pClass || !pClass->implementIxPersistable())
            {
               continue;
            }
            entities.append(getMetaData(pClass));
         }
         response.insert("entities", entities);
      }
      else
      {
         IxClass *pClass = QxClassX::getClass(m_entity);
         if (!pClass)
         {
            buildError(9999, "Entity not found : " + m_entity);
            return;
         }
         if (!pClass->implementIxPersistable())
         {
            buildError(9999, "Entity doesn't implement qx::IxPersistable interface : " + m_entity);
            return;
         }
         response.insert("entity", getMetaData(pClass));
      }
      m_responseJson = response;
   }

   QJsonValue QxRestApi::QxRestApiImpl::getMetaData(IxClass *pClass)
   {
      // Fill data about entity
      QJsonObject entity;
      entity.insert("key", pClass->getKey());
      entity.insert("name", pClass->getName());
      entity.insert("description", pClass->getDescription());
      entity.insert("version", static_cast<int>(pClass->getVersion()));
      entity.insert("base_entity", (pClass->getBaseClass() ? pClass->getBaseClass()->getKey() : QString()));

      // Fill primary key information
      IxDataMember *pDataMemberId = pClass->getId();
      QJsonObject primaryKey;
      if (pDataMemberId)
      {
         primaryKey.insert("key", pDataMemberId->getKey());
         primaryKey.insert("description", pDataMemberId->getDescription());
         primaryKey.insert("type", pDataMemberId->getType());
      }
      entity.insert("entity_id", primaryKey);

      // Prepare arrays for properties and relations
      QJsonArray properties;
      QJsonArray relations;

      // Process all data members in a single loop
      IxDataMemberX *pDataMemberX = pClass->getDataMemberX();
      if (pDataMemberX)
      {
         const long memberCount = pDataMemberX->count();
         for (long i = 0; i < memberCount; ++i)
         {
            IxDataMember *pDataMember = pDataMemberX->get(i);
            if (!pDataMember || (pDataMember == pDataMemberId))
            {
               continue;
            }

            IxSqlRelation *pRelation = pDataMember->getSqlRelation();
            if (pRelation)
            {
               // This is a relation
               QJsonObject relation;
               relation.insert("key", pDataMember->getKey());
               relation.insert("description", pDataMember->getDescription());
               relation.insert("type", pDataMember->getType());
               relation.insert("type_relation", pRelation->getDescription());
               relation.insert("target", (pRelation->getClass() ? pRelation->getClass()->getKey() : QString()));
               relations.append(relation);
            }
            else
            {
               // This is a regular property
               QJsonObject property;
               property.insert("key", pDataMember->getKey());
               property.insert("description", pDataMember->getDescription());
               property.insert("type", pDataMember->getType());
               properties.append(property);
            }
         }
      }

      entity.insert("properties", properties);
      entity.insert("relations", relations);

      return entity;
   }

   void QxRestApi::QxRestApiImpl::getDatabases()
   {
   }

#endif // _QX_NO_JSON

} // namespace qx
