index_.LookupAttachment
bool StatelessDatabaseOperations::LookupAttachment(FileInfo& attachment,
int64_t& revision,
const std::string& instancePublicId,
FileContentType contentType)
{
class Operations : public ReadOnlyOperationsT5<bool&, FileInfo&, int64_t&, const std::string&, FileContentType>
{
public:
virtual void ApplyTuple(ReadOnlyTransaction& transaction,
const Tuple& tuple) ORTHANC_OVERRIDE
{
int64_t internalId;
ResourceType type;
if (!transaction.LookupResource(internalId, type, tuple.get<3>()))
{
throw OrthancException(ErrorCode_UnknownResource);
}
else if (transaction.LookupAttachment(tuple.get<1>(), tuple.get<2>(), internalId, tuple.get<4>()))
{
assert(tuple.get<1>().GetContentType() == tuple.get<4>());
tuple.get<0>() = true;
}
else
{
tuple.get<0>() = false;
}
}
};
bool found;
Operations operations;
operations.Apply(*this, found, attachment, revision, instancePublicId, contentType);
return found;
}
template <typename T1,
typename T2,
typename T3,
typename T4,
typename T5>
class ReadOnlyOperationsT5 : public boost::noncopyable
{
public:
typedef typename boost::tuple<T1, T2, T3, T4, T5> Tuple;
virtual ~ReadOnlyOperationsT5()
{
}
virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction,
const Tuple& tuple) = 0;
void Apply(StatelessDatabaseOperations& index,
T1 t1,
T2 t2,
T3 t3,
T4 t4,
T5 t5)
{
const Tuple tuple(t1, t2, t3, t4, t5);
TupleOperationsWrapper<ReadOnlyOperationsT5, Tuple> wrapper(*this, tuple);
index.Apply(wrapper);
}
};
/**
* Some handy templates to reduce the verbosity in the definitions
* of the internal classes.
**/
template <typename Operations,
typename Tuple>
class TupleOperationsWrapper : public StatelessDatabaseOperations::IReadOnlyOperations
{
protected:
Operations& operations_;
const Tuple& tuple_;
public:
TupleOperationsWrapper(Operations& operations,
const Tuple& tuple) :
operations_(operations),
tuple_(tuple)
{
}
virtual void Apply(StatelessDatabaseOperations::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
{
operations_.ApplyTuple(transaction, tuple_);
}
};
void StatelessDatabaseOperations::Apply(IReadOnlyOperations& operations)
{
ApplyInternal(&operations, NULL);
}
void StatelessDatabaseOperations::ApplyInternal(IReadOnlyOperations* readOperations,
IReadWriteOperations* writeOperations)
{
boost::shared_lock<boost::shared_mutex> lock(mutex_); // To protect "factory_" and "maxRetries_"
if ((readOperations == NULL && writeOperations == NULL) ||
(readOperations != NULL && writeOperations != NULL))
{
throw OrthancException(ErrorCode_InternalError);
}
if (factory_.get() == NULL)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls, "No transaction context was provided");
}
unsigned int attempt = 0;
for (;;)
{
try
{
if (readOperations != NULL)
{
/**
* IMPORTANT: In Orthanc <= 1.9.1, there was no transaction
* in this case. This was OK because of the presence of the
* global mutex that was protecting the database.
**/
Transaction transaction(db_, *factory_, TransactionType_ReadOnly); // TODO - Only if not "TransactionType_Implicit"
{
ReadOnlyTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.HasLabelsSupport());
readOperations->Apply(t);
}
transaction.Commit();
}
else
{
assert(writeOperations != NULL);
Transaction transaction(db_, *factory_, TransactionType_ReadWrite);
{
ReadWriteTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.HasLabelsSupport());
writeOperations->Apply(t);
}
transaction.Commit();
}
return; // Success
}
catch (OrthancException& e)
{
if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize)
{
if (attempt >= maxRetries_)
{
throw;
}
else
{
attempt++;
// The "rand()" adds some jitter to de-synchronize writers
boost::this_thread::sleep(boost::posix_time::milliseconds(100 * attempt + 5 * (rand() % 10)));
}
}
else
{
throw;
}
}
}
}