/*
* Copyright 2017 Sony Corporation
*/
#include "Poco/Timestamp.h"
#include "easyhttpcpp/common/CoreLogger.h"
#include "easyhttpcpp/common/FileUtil.h"
#include "easyhttpcpp/db/AutoSqliteCursor.h"
#include "easyhttpcpp/db/AutoSqliteDatabase.h"
#include "easyhttpcpp/db/AutoSqliteTransaction.h"
#include "easyhttpcpp/db/ContentValues.h"
#include "easyhttpcpp/db/SqlException.h"
#include "easyhttpcpp/HttpException.h"
#include "HttpCacheDatabase.h"
#include "HttpCacheMetadata.h"
#include "HttpCacheEnumerationListener.h"
#include "HttpInternalConstants.h"
#include "HttpUtil.h"
using easyhttpcpp::common::Cache;
using easyhttpcpp::common::CacheMetadata;
using easyhttpcpp::common::FileUtil;
using easyhttpcpp::db::AutoSqliteCursor;
using easyhttpcpp::db::AutoSqliteDatabase;
using easyhttpcpp::db::AutoSqliteTransaction;
using easyhttpcpp::db::ContentValues;
using easyhttpcpp::db::SqlException;
using easyhttpcpp::db::SqliteCursor;
using easyhttpcpp::db::SqliteDatabase;
using easyhttpcpp::db::SqliteOpenHelper;
namespace easyhttpcpp {
static const std::string Tag = "HttpCacheDatabase";
HttpCacheDatabase::HttpCacheDatabase(HttpCacheDatabaseOpenHelper::Ptr pOpenHelper) : m_pOpenHelper(pOpenHelper)
{
}
HttpCacheDatabase::~HttpCacheDatabase()
{
}
HttpCacheMetadata::Ptr HttpCacheDatabase::getMetadata(const std::string& key)
{
Poco::FastMutex::ScopedLock lock(m_mutex);
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getReadableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "getMetadata() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
std::vector columns;
columns.push_back(HttpInternalConstants::Database::Key::Url);
columns.push_back(HttpInternalConstants::Database::Key::Method);
columns.push_back(HttpInternalConstants::Database::Key::StatusCode);
columns.push_back(HttpInternalConstants::Database::Key::StatusMessage);
columns.push_back(HttpInternalConstants::Database::Key::ResponseHeaderJson);
columns.push_back(HttpInternalConstants::Database::Key::ResponseBodySize);
columns.push_back(HttpInternalConstants::Database::Key::SentRequestAtEpoch);
columns.push_back(HttpInternalConstants::Database::Key::ReceivedResponseAtEpoch);
columns.push_back(HttpInternalConstants::Database::Key::CreatedAtEpoch);
std::string whereClause = std::string(HttpInternalConstants::Database::Key::CacheKey) + "=?";
std::vector whereArgs;
whereArgs.push_back(key);
SqliteCursor::Ptr pCursor = pDb->query(HttpInternalConstants::Database::TableName, &columns, &whereClause,
&whereArgs, NULL, NULL, NULL, NULL);
AutoSqliteCursor autoSqliteCursor(pCursor);
if (pCursor->moveToFirst()) {
HttpCacheMetadata::Ptr pHttpCacheMetadata = new HttpCacheMetadata();
pHttpCacheMetadata->setKey(key);
pHttpCacheMetadata->setUrl(pCursor->getString(0));
pHttpCacheMetadata->setHttpMethod(static_cast (pCursor->getInt(1)));
pHttpCacheMetadata->setStatusCode(pCursor->getInt(2));
pHttpCacheMetadata->setStatusMessage(pCursor->getString(3));
pHttpCacheMetadata->setResponseHeaders(HttpUtil::exchangeJsonStrToHeaders(pCursor->getString(4)));
pHttpCacheMetadata->setResponseBodySize(pCursor->getUnsignedLongLong(5));
pHttpCacheMetadata->setSentRequestAtEpoch(pCursor->getUnsignedLongLong(6));
pHttpCacheMetadata->setReceivedResponseAtEpoch(pCursor->getUnsignedLongLong(7));
pHttpCacheMetadata->setCreatedAtEpoch(pCursor->getUnsignedLongLong(8));
// dump
EASYHTTPCPP_LOG_D(Tag, "getMetadata");
dumpMetadata(pHttpCacheMetadata);
return pHttpCacheMetadata;
} else {
EASYHTTPCPP_LOG_D(Tag, "getMetadata(): can not get row");
return NULL;
}
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while getMetadata(): %s", e.getMessage().c_str());
throw;
}
}
bool HttpCacheDatabase::deleteMetadata(const std::string& key)
{
Poco::FastMutex::ScopedLock lock(m_mutex);
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getWritableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "deleteMetadata() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
AutoSqliteTransaction autoSqliteTransaction(pDb);
std::string whereClause = std::string(HttpInternalConstants::Database::Key::CacheKey) + "=?";
std::vector whereArgs;
whereArgs.push_back(key);
bool deleted = pDb->deleteRows(HttpInternalConstants::Database::TableName, &whereClause, &whereArgs) > 0;
pDb->setTransactionSuccessful();
return deleted;
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while deleteMetadata(): %s", e.getMessage().c_str());
// do not set the transaction as successful; database will rollback automatically
throw;
}
}
void HttpCacheDatabase::updateMetadata(const std::string& key, HttpCacheMetadata::Ptr pHttpCacheMetadata)
{
Poco::FastMutex::ScopedLock lock(m_mutex);
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getWritableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "updateMetadata() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
// always use transactions for speedy and reliable updates
AutoSqliteTransaction autoSqliteTransaction(pDb);
ContentValues values;
values.put(HttpInternalConstants::Database::Key::CacheKey, pHttpCacheMetadata->getKey());
values.put(HttpInternalConstants::Database::Key::Url, pHttpCacheMetadata->getUrl());
values.put(HttpInternalConstants::Database::Key::Method, pHttpCacheMetadata->getHttpMethod());
values.put(HttpInternalConstants::Database::Key::StatusCode, pHttpCacheMetadata->getStatusCode());
values.put(HttpInternalConstants::Database::Key::StatusMessage, pHttpCacheMetadata->getStatusMessage());
values.put(HttpInternalConstants::Database::Key::ResponseHeaderJson,
HttpUtil::exchangeHeadersToJsonStr(pHttpCacheMetadata->getResponseHeaders()));
values.put(HttpInternalConstants::Database::Key::ResponseBodySize, pHttpCacheMetadata->getResponseBodySize());
values.put(HttpInternalConstants::Database::Key::SentRequestAtEpoch,
pHttpCacheMetadata->getSentRequestAtEpoch());
values.put(HttpInternalConstants::Database::Key::ReceivedResponseAtEpoch,
pHttpCacheMetadata->getReceivedResponseAtEpoch());
values.put(HttpInternalConstants::Database::Key::CreatedAtEpoch, pHttpCacheMetadata->getCreatedAtEpoch());
Poco::Timestamp now;
values.put(HttpInternalConstants::Database::Key::LastAccessedAtEpoch, now.epochTime());
// do an INSERT, and if that INSERT fails because of a conflict,
// delete the conflicting rows before INSERTing again
pDb->replace(HttpInternalConstants::Database::TableName, values);
EASYHTTPCPP_LOG_V(Tag, "New HttpCacheMetadata updateMetadata.");
pDb->setTransactionSuccessful();
// dump
EASYHTTPCPP_LOG_D(Tag, "updateMetadata");
dumpMetadata(pHttpCacheMetadata);
EASYHTTPCPP_LOG_D(Tag, "lastAccessedAtEpoch = %s", Poco::DateTimeFormatter::format(now,
Poco::DateTimeFormat::HTTP_FORMAT).c_str());
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while updateMetadata(): %s", e.getMessage().c_str());
throw;
}
}
bool HttpCacheDatabase::updateLastAccessedSec(const std::string& key)
{
Poco::FastMutex::ScopedLock lock(m_mutex);
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getWritableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "updateMetadata() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
// always use transactions for speedy and reliable updates
AutoSqliteTransaction autoSqliteTransaction(pDb);
ContentValues values;
Poco::Timestamp now;
values.put(HttpInternalConstants::Database::Key::LastAccessedAtEpoch, now.epochTime());
EASYHTTPCPP_LOG_D(Tag, "updateLastAccessedSec: lastAccessedAtEpoch = %s", Poco::DateTimeFormatter::format(now,
Poco::DateTimeFormat::HTTP_FORMAT).c_str());
std::string whereClause = std::string(HttpInternalConstants::Database::Key::CacheKey) + "=?";
std::vector whereArgs;
whereArgs.push_back(key);
bool update;
if (pDb->update(HttpInternalConstants::Database::TableName, values, &whereClause, &whereArgs) == 0) {
// cannot update lastAccessedSec in cache, but ignore it.
EASYHTTPCPP_LOG_D(Tag, "updateLastAccessedSec : can not update database.");
update = false;
} else {
update = true;
}
pDb->setTransactionSuccessful();
return update;
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while updateLastAccessedSec(): %s", e.getMessage().c_str());
throw;
}
}
bool HttpCacheDatabase::deleteDatabaseFile()
{
Poco::FastMutex::ScopedLock lock(m_mutex);
return FileUtil::removeFileIfPresent(Poco::File(m_pOpenHelper->getDatabasePath().absolute().toString()));
}
void HttpCacheDatabase::enumerate(HttpCacheEnumerationListener* pListener)
{
// do not lock in enumerate because deleteMetadata might be called from onEnumerate callback.
// when call enumerate, exclusive control is done by the caller.
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getReadableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "enumerate() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
try {
SqliteCursor::Ptr pCursor;
{
AutoSqliteDatabase autoSqliteDatabase(pDb);
std::vector columns;
columns.push_back(HttpInternalConstants::Database::Key::CacheKey);
columns.push_back(HttpInternalConstants::Database::Key::ResponseBodySize);
// sort by LastAccessedSec.
std::string orderBy = std::string(HttpInternalConstants::Database::Key::LastAccessedAtEpoch) + " ASC";
pCursor = pDb->query(HttpInternalConstants::Database::TableName, &columns, NULL, NULL, NULL,
NULL, &orderBy, NULL);
}
AutoSqliteCursor autoSqliteCursor(pCursor);
if (pCursor->moveToFirst()) {
do {
HttpCacheEnumerationListener::EnumerationParam param;
param.m_key = pCursor->getString(0);
param.m_responseBodySize = pCursor->getUnsignedLongLong(1);
if (!pListener->onEnumerate(param)) {
EASYHTTPCPP_LOG_D(Tag, "enumerate: error occurred onEnumerate.");
// could not store it in cache, but continue processing because it is cache.
}
} while (pCursor->moveToNext());
}
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while enumerate(): %s", e.getMessage().c_str());
throw;
}
}
void HttpCacheDatabase::closeSqliteSession()
{
m_pOpenHelper->closeSqliteSession();
}
HttpCacheDatabase::HttpCacheMetadataAll::HttpCacheMetadataAll() : m_lastAccessedAtEpoch(0)
{
}
void HttpCacheDatabase::HttpCacheMetadataAll::setLastAccessedAtEpoch(std::time_t lastAccessedAtEpoch)
{
m_lastAccessedAtEpoch = lastAccessedAtEpoch;
}
std::time_t HttpCacheDatabase::HttpCacheMetadataAll::getLastAccessedAtEpoch() const
{
return m_lastAccessedAtEpoch;
}
HttpCacheDatabase::HttpCacheMetadataAll::Ptr HttpCacheDatabase::getMetadataAll(const std::string& key)
{
// do not use lock because this method is for test.
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getReadableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "getMetadataAll() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
std::vector columns;
columns.push_back(HttpInternalConstants::Database::Key::Url);
columns.push_back(HttpInternalConstants::Database::Key::Method);
columns.push_back(HttpInternalConstants::Database::Key::StatusCode);
columns.push_back(HttpInternalConstants::Database::Key::StatusMessage);
columns.push_back(HttpInternalConstants::Database::Key::ResponseHeaderJson);
columns.push_back(HttpInternalConstants::Database::Key::ResponseBodySize);
columns.push_back(HttpInternalConstants::Database::Key::SentRequestAtEpoch);
columns.push_back(HttpInternalConstants::Database::Key::ReceivedResponseAtEpoch);
columns.push_back(HttpInternalConstants::Database::Key::CreatedAtEpoch);
columns.push_back(HttpInternalConstants::Database::Key::LastAccessedAtEpoch);
std::string whereClause = std::string(HttpInternalConstants::Database::Key::CacheKey) + "=?";
std::vector whereArgs;
whereArgs.push_back(key);
SqliteCursor::Ptr pCursor = pDb->query(HttpInternalConstants::Database::TableName, &columns, &whereClause,
&whereArgs, NULL, NULL, NULL, NULL);
AutoSqliteCursor autoSqliteCursor(pCursor);
if (pCursor->moveToFirst()) {
HttpCacheDatabase::HttpCacheMetadataAll::Ptr pHttpCacheMetadataAll =
new HttpCacheDatabase::HttpCacheMetadataAll();
pHttpCacheMetadataAll->setKey(key);
pHttpCacheMetadataAll->setUrl(pCursor->getString(0));
pHttpCacheMetadataAll->setHttpMethod(static_cast (pCursor->getInt(1)));
pHttpCacheMetadataAll->setStatusCode(pCursor->getInt(2));
pHttpCacheMetadataAll->setStatusMessage(pCursor->getString(3));
pHttpCacheMetadataAll->setResponseHeaders(HttpUtil::exchangeJsonStrToHeaders(pCursor->getString(4)));
pHttpCacheMetadataAll->setResponseBodySize(pCursor->getUnsignedLongLong(5));
pHttpCacheMetadataAll->setSentRequestAtEpoch(pCursor->getUnsignedLongLong(6));
pHttpCacheMetadataAll->setReceivedResponseAtEpoch(pCursor->getUnsignedLongLong(7));
pHttpCacheMetadataAll->setCreatedAtEpoch(pCursor->getUnsignedLongLong(8));
pHttpCacheMetadataAll->setLastAccessedAtEpoch(pCursor->getUnsignedLongLong(9));
return pHttpCacheMetadataAll;
} else {
EASYHTTPCPP_LOG_D(Tag, "getMetadataAll(): can not get row");
return NULL;
}
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while getMetadataAll(): %s", e.getMessage().c_str());
throw;
}
}
void HttpCacheDatabase::updateMetadataAll(const std::string& key,
HttpCacheDatabase::HttpCacheMetadataAll::Ptr pHttpCacheMetadataAll)
{
// do not use lock because this method is for test.
SqliteDatabase::Ptr pDb;
try {
pDb = m_pOpenHelper->getWritableDatabase();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "updateMetadataAll() Unable to open database. Error: %s", e.getMessage().c_str());
throw;
}
AutoSqliteDatabase autoSqliteDatabase(pDb);
try {
// always use transactions for speedy and reliable updates
AutoSqliteTransaction autoSqliteTransaction(pDb);
ContentValues values;
values.put(HttpInternalConstants::Database::Key::CacheKey, pHttpCacheMetadataAll->getKey());
values.put(HttpInternalConstants::Database::Key::Url, pHttpCacheMetadataAll->getUrl());
values.put(HttpInternalConstants::Database::Key::Method, pHttpCacheMetadataAll->getHttpMethod());
values.put(HttpInternalConstants::Database::Key::StatusCode, pHttpCacheMetadataAll->getStatusCode());
values.put(HttpInternalConstants::Database::Key::StatusMessage, pHttpCacheMetadataAll->getStatusMessage());
values.put(HttpInternalConstants::Database::Key::ResponseHeaderJson,
HttpUtil::exchangeHeadersToJsonStr(pHttpCacheMetadataAll->getResponseHeaders()));
values.put(HttpInternalConstants::Database::Key::ResponseBodySize,
pHttpCacheMetadataAll->getResponseBodySize());
values.put(HttpInternalConstants::Database::Key::SentRequestAtEpoch,
pHttpCacheMetadataAll->getSentRequestAtEpoch());
values.put(HttpInternalConstants::Database::Key::ReceivedResponseAtEpoch,
pHttpCacheMetadataAll->getReceivedResponseAtEpoch());
values.put(HttpInternalConstants::Database::Key::CreatedAtEpoch, pHttpCacheMetadataAll->getCreatedAtEpoch());
values.put(HttpInternalConstants::Database::Key::LastAccessedAtEpoch,
pHttpCacheMetadataAll->getLastAccessedAtEpoch());
// do an INSERT, and if that INSERT fails because of a conflict,
// delete the conflicting rows before INSERTing again
pDb->replace(HttpInternalConstants::Database::TableName, values);
EASYHTTPCPP_LOG_V(Tag, "New HttpCacheMetadata updateMetadataAll.");
pDb->setTransactionSuccessful();
} catch (const SqlException& e) {
EASYHTTPCPP_LOG_D(Tag, "SQLite error while updateMetadataAll(): %s", e.getMessage().c_str());
throw;
}
}
void HttpCacheDatabase::dumpMetadata(HttpCacheMetadata::Ptr pHttpCacheMetadata)
{
EASYHTTPCPP_LOG_D(Tag, "url = %s", pHttpCacheMetadata->getUrl().c_str());
EASYHTTPCPP_LOG_D(Tag, "method = %s", HttpUtil::httpMethodToString(pHttpCacheMetadata->getHttpMethod()).c_str());
EASYHTTPCPP_LOG_D(Tag, "code = %d", pHttpCacheMetadata->getStatusCode());
EASYHTTPCPP_LOG_D(Tag, "message = %s", pHttpCacheMetadata->getStatusMessage().c_str());
EASYHTTPCPP_LOG_D(Tag, "header = %s", HttpUtil::exchangeHeadersToJsonStr(
pHttpCacheMetadata->getResponseHeaders()).c_str());
EASYHTTPCPP_LOG_D(Tag, "body size = %zu", pHttpCacheMetadata->getResponseBodySize());
Poco::Timestamp sentRequestTime = Poco::Timestamp::fromEpochTime(pHttpCacheMetadata->getSentRequestAtEpoch());
EASYHTTPCPP_LOG_D(Tag, "sentRequestAtEpoch = %s", Poco::DateTimeFormatter::format(sentRequestTime,
Poco::DateTimeFormat::HTTP_FORMAT).c_str());
Poco::Timestamp receiveedResponseTime =
Poco::Timestamp::fromEpochTime(pHttpCacheMetadata->getReceivedResponseAtEpoch());
EASYHTTPCPP_LOG_D(Tag, "receivedResponseAtEpoch = %s", Poco::DateTimeFormatter::format(receiveedResponseTime,
Poco::DateTimeFormat::HTTP_FORMAT).c_str());
Poco::Timestamp createMetadataTime = Poco::Timestamp::fromEpochTime(pHttpCacheMetadata->getCreatedAtEpoch());
EASYHTTPCPP_LOG_D(Tag, "createAtEpoch = %s", Poco::DateTimeFormatter::format(createMetadataTime,
Poco::DateTimeFormat::HTTP_FORMAT).c_str());
}
} /* namespace easyhttpcpp */