fork of https://github.com/oxigraph/rocksdb and https://github.com/facebook/rocksdb for nextgraph and oxigraph
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
519 lines
18 KiB
519 lines
18 KiB
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// This file implements the callback "bridge" between Java and C++ for
|
|
// ROCKSDB_NAMESPACE::Comparator.
|
|
|
|
#include "rocksjni/writebatchhandlerjnicallback.h"
|
|
|
|
#include "rocksjni/portal.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
WriteBatchHandlerJniCallback::WriteBatchHandlerJniCallback(
|
|
JNIEnv* env, jobject jWriteBatchHandler)
|
|
: JniCallback(env, jWriteBatchHandler), m_env(env) {
|
|
m_jPutCfMethodId = WriteBatchHandlerJni::getPutCfMethodId(env);
|
|
if (m_jPutCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jPutMethodId = WriteBatchHandlerJni::getPutMethodId(env);
|
|
if (m_jPutMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMergeCfMethodId = WriteBatchHandlerJni::getMergeCfMethodId(env);
|
|
if (m_jMergeCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMergeMethodId = WriteBatchHandlerJni::getMergeMethodId(env);
|
|
if (m_jMergeMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jDeleteCfMethodId = WriteBatchHandlerJni::getDeleteCfMethodId(env);
|
|
if (m_jDeleteCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jDeleteMethodId = WriteBatchHandlerJni::getDeleteMethodId(env);
|
|
if (m_jDeleteMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jSingleDeleteCfMethodId =
|
|
WriteBatchHandlerJni::getSingleDeleteCfMethodId(env);
|
|
if (m_jSingleDeleteCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jSingleDeleteMethodId = WriteBatchHandlerJni::getSingleDeleteMethodId(env);
|
|
if (m_jSingleDeleteMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jDeleteRangeCfMethodId =
|
|
WriteBatchHandlerJni::getDeleteRangeCfMethodId(env);
|
|
if (m_jDeleteRangeCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jDeleteRangeMethodId = WriteBatchHandlerJni::getDeleteRangeMethodId(env);
|
|
if (m_jDeleteRangeMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jLogDataMethodId = WriteBatchHandlerJni::getLogDataMethodId(env);
|
|
if (m_jLogDataMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jPutBlobIndexCfMethodId =
|
|
WriteBatchHandlerJni::getPutBlobIndexCfMethodId(env);
|
|
if (m_jPutBlobIndexCfMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkBeginPrepareMethodId =
|
|
WriteBatchHandlerJni::getMarkBeginPrepareMethodId(env);
|
|
if (m_jMarkBeginPrepareMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkEndPrepareMethodId =
|
|
WriteBatchHandlerJni::getMarkEndPrepareMethodId(env);
|
|
if (m_jMarkEndPrepareMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkNoopMethodId = WriteBatchHandlerJni::getMarkNoopMethodId(env);
|
|
if (m_jMarkNoopMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkRollbackMethodId = WriteBatchHandlerJni::getMarkRollbackMethodId(env);
|
|
if (m_jMarkRollbackMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkCommitMethodId = WriteBatchHandlerJni::getMarkCommitMethodId(env);
|
|
if (m_jMarkCommitMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jMarkCommitWithTimestampMethodId =
|
|
WriteBatchHandlerJni::getMarkCommitWithTimestampMethodId(env);
|
|
if (m_jMarkCommitWithTimestampMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
|
|
m_jContinueMethodId = WriteBatchHandlerJni::getContinueMethodId(env);
|
|
if (m_jContinueMethodId == nullptr) {
|
|
// exception thrown
|
|
return;
|
|
}
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::PutCF(
|
|
uint32_t column_family_id, const Slice& key, const Slice& value) {
|
|
auto put = [this, column_family_id](jbyteArray j_key, jbyteArray j_value) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jPutCfMethodId,
|
|
static_cast<jint>(column_family_id), j_key, j_value);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::kv_op(key, value, put);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::Put(const Slice& key, const Slice& value) {
|
|
auto put = [this](jbyteArray j_key, jbyteArray j_value) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jPutMethodId, j_key, j_value);
|
|
};
|
|
WriteBatchHandlerJniCallback::kv_op(key, value, put);
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MergeCF(
|
|
uint32_t column_family_id, const Slice& key, const Slice& value) {
|
|
auto merge = [this, column_family_id](jbyteArray j_key, jbyteArray j_value) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMergeCfMethodId,
|
|
static_cast<jint>(column_family_id), j_key, j_value);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::kv_op(key, value, merge);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::Merge(const Slice& key, const Slice& value) {
|
|
auto merge = [this](jbyteArray j_key, jbyteArray j_value) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMergeMethodId, j_key, j_value);
|
|
};
|
|
WriteBatchHandlerJniCallback::kv_op(key, value, merge);
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::DeleteCF(
|
|
uint32_t column_family_id, const Slice& key) {
|
|
auto remove = [this, column_family_id](jbyteArray j_key) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteCfMethodId,
|
|
static_cast<jint>(column_family_id), j_key);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::k_op(key, remove);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::Delete(const Slice& key) {
|
|
auto remove = [this](jbyteArray j_key) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteMethodId, j_key);
|
|
};
|
|
WriteBatchHandlerJniCallback::k_op(key, remove);
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::SingleDeleteCF(
|
|
uint32_t column_family_id, const Slice& key) {
|
|
auto singleDelete = [this, column_family_id](jbyteArray j_key) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jSingleDeleteCfMethodId,
|
|
static_cast<jint>(column_family_id), j_key);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::k_op(key, singleDelete);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::SingleDelete(const Slice& key) {
|
|
auto singleDelete = [this](jbyteArray j_key) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jSingleDeleteMethodId, j_key);
|
|
};
|
|
WriteBatchHandlerJniCallback::k_op(key, singleDelete);
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::DeleteRangeCF(
|
|
uint32_t column_family_id, const Slice& beginKey, const Slice& endKey) {
|
|
auto deleteRange = [this, column_family_id](jbyteArray j_beginKey,
|
|
jbyteArray j_endKey) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteRangeCfMethodId,
|
|
static_cast<jint>(column_family_id), j_beginKey,
|
|
j_endKey);
|
|
};
|
|
auto status =
|
|
WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::DeleteRange(const Slice& beginKey,
|
|
const Slice& endKey) {
|
|
auto deleteRange = [this](jbyteArray j_beginKey, jbyteArray j_endKey) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jDeleteRangeMethodId, j_beginKey,
|
|
j_endKey);
|
|
};
|
|
WriteBatchHandlerJniCallback::kv_op(beginKey, endKey, deleteRange);
|
|
}
|
|
|
|
void WriteBatchHandlerJniCallback::LogData(const Slice& blob) {
|
|
auto logData = [this](jbyteArray j_blob) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jLogDataMethodId, j_blob);
|
|
};
|
|
WriteBatchHandlerJniCallback::k_op(blob, logData);
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::PutBlobIndexCF(
|
|
uint32_t column_family_id, const Slice& key, const Slice& value) {
|
|
auto putBlobIndex = [this, column_family_id](jbyteArray j_key,
|
|
jbyteArray j_value) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jPutBlobIndexCfMethodId,
|
|
static_cast<jint>(column_family_id), j_key, j_value);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::kv_op(key, value, putBlobIndex);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkBeginPrepare(
|
|
bool unprepare) {
|
|
#ifndef DEBUG
|
|
(void)unprepare;
|
|
#else
|
|
assert(!unprepare);
|
|
#endif
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkBeginPrepareMethodId);
|
|
|
|
// check for Exception, in-particular RocksDBException
|
|
if (m_env->ExceptionCheck()) {
|
|
// exception thrown
|
|
jthrowable exception = m_env->ExceptionOccurred();
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> status =
|
|
ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception);
|
|
if (status == nullptr) {
|
|
// unkown status or exception occurred extracting status
|
|
m_env->ExceptionDescribe();
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) probably need a
|
|
// better error code here
|
|
|
|
} else {
|
|
m_env->ExceptionClear(); // clear the exception, as we have extracted the
|
|
// status
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
return ROCKSDB_NAMESPACE::Status::OK();
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkEndPrepare(
|
|
const Slice& xid) {
|
|
auto markEndPrepare = [this](jbyteArray j_xid) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkEndPrepareMethodId, j_xid);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::k_op(xid, markEndPrepare);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkNoop(
|
|
bool empty_batch) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkNoopMethodId,
|
|
static_cast<jboolean>(empty_batch));
|
|
|
|
// check for Exception, in-particular RocksDBException
|
|
if (m_env->ExceptionCheck()) {
|
|
// exception thrown
|
|
jthrowable exception = m_env->ExceptionOccurred();
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> status =
|
|
ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception);
|
|
if (status == nullptr) {
|
|
// unkown status or exception occurred extracting status
|
|
m_env->ExceptionDescribe();
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) probably need a
|
|
// better error code here
|
|
|
|
} else {
|
|
m_env->ExceptionClear(); // clear the exception, as we have extracted the
|
|
// status
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
return ROCKSDB_NAMESPACE::Status::OK();
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkRollback(
|
|
const Slice& xid) {
|
|
auto markRollback = [this](jbyteArray j_xid) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkRollbackMethodId, j_xid);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::k_op(xid, markRollback);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkCommit(
|
|
const Slice& xid) {
|
|
auto markCommit = [this](jbyteArray j_xid) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkCommitMethodId, j_xid);
|
|
};
|
|
auto status = WriteBatchHandlerJniCallback::k_op(xid, markCommit);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
ROCKSDB_NAMESPACE::Status WriteBatchHandlerJniCallback::MarkCommitWithTimestamp(
|
|
const Slice& xid, const Slice& ts) {
|
|
auto markCommitWithTimestamp = [this](jbyteArray j_xid, jbyteArray j_ts) {
|
|
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkCommitWithTimestampMethodId,
|
|
j_xid, j_ts);
|
|
};
|
|
auto status =
|
|
WriteBatchHandlerJniCallback::kv_op(xid, ts, markCommitWithTimestamp);
|
|
if (status == nullptr) {
|
|
return ROCKSDB_NAMESPACE::Status::OK(); // TODO(AR) what to do if there is
|
|
// an Exception but we don't know
|
|
// the ROCKSDB_NAMESPACE::Status?
|
|
} else {
|
|
return ROCKSDB_NAMESPACE::Status(*status);
|
|
}
|
|
}
|
|
|
|
bool WriteBatchHandlerJniCallback::Continue() {
|
|
jboolean jContinue =
|
|
m_env->CallBooleanMethod(m_jcallback_obj, m_jContinueMethodId);
|
|
if (m_env->ExceptionCheck()) {
|
|
// exception thrown
|
|
m_env->ExceptionDescribe();
|
|
}
|
|
|
|
return static_cast<bool>(jContinue == JNI_TRUE);
|
|
}
|
|
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> WriteBatchHandlerJniCallback::kv_op(
|
|
const Slice& key, const Slice& value,
|
|
std::function<void(jbyteArray, jbyteArray)> kvFn) {
|
|
const jbyteArray j_key = JniUtil::copyBytes(m_env, key);
|
|
if (j_key == nullptr) {
|
|
// exception thrown
|
|
if (m_env->ExceptionCheck()) {
|
|
m_env->ExceptionDescribe();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const jbyteArray j_value = JniUtil::copyBytes(m_env, value);
|
|
if (j_value == nullptr) {
|
|
// exception thrown
|
|
if (m_env->ExceptionCheck()) {
|
|
m_env->ExceptionDescribe();
|
|
}
|
|
if (j_key != nullptr) {
|
|
m_env->DeleteLocalRef(j_key);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
kvFn(j_key, j_value);
|
|
|
|
// check for Exception, in-particular RocksDBException
|
|
if (m_env->ExceptionCheck()) {
|
|
if (j_value != nullptr) {
|
|
m_env->DeleteLocalRef(j_value);
|
|
}
|
|
if (j_key != nullptr) {
|
|
m_env->DeleteLocalRef(j_key);
|
|
}
|
|
|
|
// exception thrown
|
|
jthrowable exception = m_env->ExceptionOccurred();
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> status =
|
|
ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception);
|
|
if (status == nullptr) {
|
|
// unkown status or exception occurred extracting status
|
|
m_env->ExceptionDescribe();
|
|
return nullptr;
|
|
|
|
} else {
|
|
m_env->ExceptionClear(); // clear the exception, as we have extracted the
|
|
// status
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (j_value != nullptr) {
|
|
m_env->DeleteLocalRef(j_value);
|
|
}
|
|
if (j_key != nullptr) {
|
|
m_env->DeleteLocalRef(j_key);
|
|
}
|
|
|
|
// all OK
|
|
return std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
|
|
new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
|
|
}
|
|
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> WriteBatchHandlerJniCallback::k_op(
|
|
const Slice& key, std::function<void(jbyteArray)> kFn) {
|
|
const jbyteArray j_key = JniUtil::copyBytes(m_env, key);
|
|
if (j_key == nullptr) {
|
|
// exception thrown
|
|
if (m_env->ExceptionCheck()) {
|
|
m_env->ExceptionDescribe();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
kFn(j_key);
|
|
|
|
// check for Exception, in-particular RocksDBException
|
|
if (m_env->ExceptionCheck()) {
|
|
if (j_key != nullptr) {
|
|
m_env->DeleteLocalRef(j_key);
|
|
}
|
|
|
|
// exception thrown
|
|
jthrowable exception = m_env->ExceptionOccurred();
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::Status> status =
|
|
ROCKSDB_NAMESPACE::RocksDBExceptionJni::toCppStatus(m_env, exception);
|
|
if (status == nullptr) {
|
|
// unkown status or exception occurred extracting status
|
|
m_env->ExceptionDescribe();
|
|
return nullptr;
|
|
|
|
} else {
|
|
m_env->ExceptionClear(); // clear the exception, as we have extracted the
|
|
// status
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (j_key != nullptr) {
|
|
m_env->DeleteLocalRef(j_key);
|
|
}
|
|
|
|
// all OK
|
|
return std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
|
|
new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
|
|
}
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|