Orthanc/OrthancServer/Sources/OrthancConfiguration.cpp
2025-06-23 19:07:37 +05:30

1300 lines
36 KiB
C++

/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "PrecompiledHeadersServer.h"
#include "OrthancConfiguration.h"
#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
#include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
#include "../../OrthancFramework/Sources/HttpServer/HttpServer.h"
#include "../../OrthancFramework/Sources/Logging.h"
#include "../../OrthancFramework/Sources/OrthancException.h"
#include "../../OrthancFramework/Sources/SystemToolbox.h"
#include "../../OrthancFramework/Sources/TemporaryFile.h"
#include "../../OrthancFramework/Sources/Toolbox.h"
#include "ServerIndex.h"
#include <boost/regex.hpp>
static const char* const DICOM_MODALITIES = "DicomModalities";
static const char* const DICOM_MODALITIES_IN_DB = "DicomModalitiesInDatabase";
static const char* const ORTHANC_PEERS = "OrthancPeers";
static const char* const ORTHANC_PEERS_IN_DB = "OrthancPeersInDatabase";
static const char* const TEMPORARY_DIRECTORY = "TemporaryDirectory";
static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier";
static const char* const WARNINGS = "Warnings";
static const char* const JOBS_ENGINE_THREADS_COUNT = "JobsEngineThreadsCount";
static const char* const DICOM_LOSSY_TRANSCODING_QUALITY = "DicomLossyTranscodingQuality";
namespace Orthanc
{
static void AddFileToConfiguration(Json::Value& target,
const boost::filesystem::path& path)
{
std::map<std::string, std::string> env;
SystemToolbox::GetEnvironmentVariables(env);
LOG(WARNING) << "Reading the configuration from: " << path;
Json::Value config;
{
std::string content;
SystemToolbox::ReadFile(content, path.string());
content = Toolbox::SubstituteVariables(content, env);
Json::Value tmp;
if (!Toolbox::ReadJson(tmp, content) ||
tmp.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadJson,
"The configuration file does not follow the JSON syntax: " + path.string());
}
Toolbox::CopyJsonWithoutComments(config, tmp);
}
if (target.size() == 0)
{
target = config;
}
else
{
// Merge the newly-added file with the previous content of "target"
Json::Value::Members members = config.getMemberNames();
for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
{
if (target.isMember(members[i]))
{
throw OrthancException(ErrorCode_BadFileFormat,
"The configuration section \"" + members[i] +
"\" is defined in 2 different configuration files");
}
else
{
target[members[i]] = config[members[i]];
}
}
}
}
static void ScanFolderForConfiguration(Json::Value& target,
const char* folder)
{
using namespace boost::filesystem;
LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files";
directory_iterator end_it; // default construction yields past-the-end
for (directory_iterator it(folder);
it != end_it;
++it)
{
if (!is_directory(it->status()))
{
std::string extension = it->path().extension().string();
Toolbox::ToLowerCase(extension);
if (extension == ".json")
{
AddFileToConfiguration(target, it->path().string());
}
}
}
}
static void ReadConfiguration(Json::Value& target,
const char* configurationFile)
{
target = Json::objectValue;
if (configurationFile != NULL)
{
if (!boost::filesystem::exists(configurationFile))
{
throw OrthancException(ErrorCode_InexistentFile,
"Inexistent path to configuration: " +
std::string(configurationFile));
}
if (boost::filesystem::is_directory(configurationFile))
{
ScanFolderForConfiguration(target, configurationFile);
}
else
{
AddFileToConfiguration(target, configurationFile);
}
}
else
{
#if ORTHANC_STANDALONE == 1
// No default path for the standalone configuration
LOG(WARNING) << "Using the default Orthanc configuration";
return;
#else
// In a non-standalone build, we use the
// "Resources/Configuration.json" from the Orthanc source code
boost::filesystem::path p = ORTHANC_PATH;
p /= "Resources";
p /= "Configuration.json";
AddFileToConfiguration(target, p);
#endif
}
}
static void CheckAlphanumeric(const std::string& s)
{
for (size_t j = 0; j < s.size(); j++)
{
if (!isalnum(s[j]) &&
s[j] != '-' && s[j] != '_')
{
throw OrthancException(ErrorCode_BadFileFormat,
"Only alphanumeric, dash characters and underscores are allowed "
"in the names of modalities/peers, but found: " + s);
}
}
}
void OrthancConfiguration::LoadModalitiesFromJson(const Json::Value& source)
{
modalities_.clear();
if (source.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat,
"Bad format of the \"" + std::string(DICOM_MODALITIES) +
"\" configuration section");
}
Json::Value::Members members = source.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& name = members[i];
CheckAlphanumeric(name);
RemoteModalityParameters modality;
modality.Unserialize(source[name]);
modalities_[name] = modality;
}
}
void OrthancConfiguration::LoadPeersFromJson(const Json::Value& source)
{
peers_.clear();
if (source.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat,
"Bad format of the \"" + std::string(ORTHANC_PEERS) +
"\" configuration section");
}
Json::Value::Members members = source.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& name = members[i];
CheckAlphanumeric(name);
WebServiceParameters peer;
peer.Unserialize(source[name]);
peers_[name] = peer;
}
}
void OrthancConfiguration::LoadModalities()
{
if (GetBooleanParameter(DICOM_MODALITIES_IN_DB, false))
{
// Modalities are stored in the database
if (serverIndex_ == NULL)
{
throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
std::string property = serverIndex_->GetGlobalProperty(GlobalProperty_Modalities, false /* not shared */, "{}");
Json::Value modalities;
if (Toolbox::ReadJson(modalities, property))
{
LoadModalitiesFromJson(modalities);
}
else
{
throw OrthancException(ErrorCode_InternalError,
"Cannot unserialize the list of modalities from the Orthanc database");
}
}
}
else
{
// Modalities are stored in the configuration files
if (json_.isMember(DICOM_MODALITIES))
{
LoadModalitiesFromJson(json_[DICOM_MODALITIES]);
}
else
{
modalities_.clear();
}
}
}
void OrthancConfiguration::LoadJobsEngineThreadsCount()
{
// default values
jobsEngineThreadsCount_["ResourceModification"] = 1;
if (json_.isMember(JOBS_ENGINE_THREADS_COUNT))
{
const Json::Value& source = json_[JOBS_ENGINE_THREADS_COUNT];
if (source.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat,
"Bad format of the \"" + std::string(JOBS_ENGINE_THREADS_COUNT) +
"\" configuration section");
}
Json::Value::Members members = source.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& name = members[i];
if (!source[name].isUInt())
{
throw OrthancException(ErrorCode_BadFileFormat,
"Bad format for \"" + std::string(JOBS_ENGINE_THREADS_COUNT) + "." + name +
"\". It should be an unsigned integer");
}
jobsEngineThreadsCount_[name] = source[name].asUInt();
}
}
}
unsigned int OrthancConfiguration::GetJobsEngineWorkersThread(const std::string& jobType) const
{
unsigned int workersThread = 1;
const JobsEngineThreadsCount::const_iterator it = jobsEngineThreadsCount_.find(jobType);
if (it != jobsEngineThreadsCount_.end())
{
workersThread = it->second;
}
if (workersThread == 0)
{
workersThread = SystemToolbox::GetHardwareConcurrency();
}
return workersThread;
}
void OrthancConfiguration::LoadPeers()
{
if (GetBooleanParameter(ORTHANC_PEERS_IN_DB, false))
{
// Peers are stored in the database
if (serverIndex_ == NULL)
{
throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
std::string property = serverIndex_->GetGlobalProperty(GlobalProperty_Peers, false /* not shared */, "{}");
Json::Value peers;
if (Toolbox::ReadJson(peers, property))
{
LoadPeersFromJson(peers);
}
else
{
throw OrthancException(ErrorCode_InternalError,
"Cannot unserialize the list of peers from the Orthanc database");
}
}
}
else
{
// Peers are stored in the configuration files
if (json_.isMember(ORTHANC_PEERS))
{
LoadPeersFromJson(json_[ORTHANC_PEERS]);
}
else
{
peers_.clear();
}
}
}
void OrthancConfiguration::SaveModalitiesToJson(Json::Value& target)
{
target = Json::objectValue;
for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
{
Json::Value modality;
it->second.Serialize(modality, true /* force advanced format */);
target[it->first] = modality;
}
}
void OrthancConfiguration::SavePeersToJson(Json::Value& target)
{
target = Json::objectValue;
for (Peers::const_iterator it = peers_.begin(); it != peers_.end(); ++it)
{
Json::Value peer;
it->second.Serialize(peer,
false /* use simple format if possible */,
true /* include passwords */);
target[it->first] = peer;
}
}
void OrthancConfiguration::SaveModalities()
{
if (GetBooleanParameter(DICOM_MODALITIES_IN_DB, false))
{
// Modalities are stored in the database
if (serverIndex_ == NULL)
{
throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
Json::Value modalities;
SaveModalitiesToJson(modalities);
std::string s;
Toolbox::WriteFastJson(s, modalities);
serverIndex_->SetGlobalProperty(GlobalProperty_Modalities, false /* not shared */, s);
}
}
else
{
// Modalities are stored in the configuration files
if (!modalities_.empty() ||
json_.isMember(DICOM_MODALITIES))
{
SaveModalitiesToJson(json_[DICOM_MODALITIES]);
}
}
}
void OrthancConfiguration::SavePeers()
{
if (GetBooleanParameter(ORTHANC_PEERS_IN_DB, false))
{
// Peers are stored in the database
if (serverIndex_ == NULL)
{
throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
Json::Value peers;
SavePeersToJson(peers);
std::string s;
Toolbox::WriteFastJson(s, peers);
serverIndex_->SetGlobalProperty(GlobalProperty_Peers, false /* not shared */, s);
}
}
else
{
// Peers are stored in the configuration files
if (!peers_.empty() ||
json_.isMember(ORTHANC_PEERS))
{
SavePeersToJson(json_[ORTHANC_PEERS]);
}
}
}
OrthancConfiguration& OrthancConfiguration::GetInstance()
{
static OrthancConfiguration configuration;
return configuration;
}
bool OrthancConfiguration::LookupStringParameter(std::string& target,
const std::string& parameter) const
{
if (json_.isMember(parameter))
{
if (json_[parameter].type() != Json::stringValue)
{
throw OrthancException(ErrorCode_BadParameterType,
"The configuration option \"" + parameter + "\" must be a string");
}
else
{
target = json_[parameter].asString();
return true;
}
}
else
{
return false;
}
}
std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
const std::string& defaultValue) const
{
std::string value;
if (LookupStringParameter(value, parameter))
{
return value;
}
else
{
return defaultValue;
}
}
int OrthancConfiguration::GetIntegerParameter(const std::string& parameter,
int defaultValue) const
{
if (json_.isMember(parameter))
{
if (json_[parameter].type() != Json::intValue)
{
throw OrthancException(ErrorCode_BadParameterType,
"The configuration option \"" + parameter + "\" must be an integer");
}
else
{
return json_[parameter].asInt();
}
}
else
{
return defaultValue;
}
}
unsigned int OrthancConfiguration::GetUnsignedIntegerParameter(
const std::string& parameter,
unsigned int defaultValue) const
{
int v = GetIntegerParameter(parameter, defaultValue);
if (v < 0)
{
throw OrthancException(ErrorCode_ParameterOutOfRange,
"The configuration option \"" + parameter + "\" must be a positive integer");
}
else
{
return static_cast<unsigned int>(v);
}
}
bool OrthancConfiguration::LookupBooleanParameter(bool& target,
const std::string& parameter) const
{
if (json_.isMember(parameter))
{
if (json_[parameter].type() != Json::booleanValue)
{
throw OrthancException(ErrorCode_BadParameterType,
"The configuration option \"" + parameter +
"\" must be a Boolean (true or false)");
}
else
{
target = json_[parameter].asBool();
return true;
}
}
else
{
return false;
}
}
bool OrthancConfiguration::GetBooleanParameter(const std::string& parameter,
bool defaultValue) const
{
bool value;
if (LookupBooleanParameter(value, parameter))
{
return value;
}
else
{
return defaultValue;
}
}
void OrthancConfiguration::Read(const char* configurationFile)
{
// Read the content of the configuration
configurationFileArg_ = configurationFile;
ReadConfiguration(json_, configurationFile);
// Adapt the paths to the configurations
defaultDirectory_ = boost::filesystem::current_path();
configurationAbsolutePath_ = "";
if (configurationFile)
{
if (boost::filesystem::is_directory(configurationFile))
{
defaultDirectory_ = boost::filesystem::path(configurationFile);
configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string();
}
else
{
defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path();
configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string();
}
}
else
{
#if ORTHANC_STANDALONE != 1
// In a non-standalone build, we use the
// "Resources/Configuration.json" from the Orthanc source code
boost::filesystem::path p = ORTHANC_PATH;
p /= "Resources";
p /= "Configuration.json";
configurationAbsolutePath_ = boost::filesystem::absolute(p).string();
#endif
}
}
void OrthancConfiguration::LoadModalitiesAndPeers()
{
if (serverIndex_ == NULL)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
LoadModalities();
LoadPeers();
}
}
void OrthancConfiguration::RegisterFont(ServerResources::FileResourceId resource)
{
std::string content;
ServerResources::GetFileResource(content, resource);
fontRegistry_.AddFromMemory(content);
}
void OrthancConfiguration::GetDicomModalityUsingSymbolicName(
RemoteModalityParameters& modality,
const std::string& name) const
{
Modalities::const_iterator found = modalities_.find(name);
if (found == modalities_.end())
{
throw OrthancException(ErrorCode_InexistentItem,
"No modality with symbolic name: " + name);
}
else
{
modality = found->second;
}
}
bool OrthancConfiguration::LookupOrthancPeer(WebServiceParameters& peer,
const std::string& name) const
{
Peers::const_iterator found = peers_.find(name);
if (found == peers_.end())
{
LOG(ERROR) << "No peer with symbolic name: " << name;
return false;
}
else
{
peer = found->second;
return true;
}
}
void OrthancConfiguration::GetListOfDicomModalities(std::set<std::string>& target) const
{
target.clear();
for (Modalities::const_iterator
it = modalities_.begin(); it != modalities_.end(); ++it)
{
target.insert(it->first);
}
}
void OrthancConfiguration::GetListOfOrthancPeers(std::set<std::string>& target) const
{
target.clear();
for (Peers::const_iterator it = peers_.begin(); it != peers_.end(); ++it)
{
target.insert(it->first);
}
}
unsigned int OrthancConfiguration::GetDicomLossyTranscodingQuality() const
{
return GetUnsignedIntegerParameter(DICOM_LOSSY_TRANSCODING_QUALITY, 90);
}
bool OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const
{
httpServer.ClearUsers();
if (!json_.isMember("RegisteredUsers"))
{
return false;
}
const Json::Value& users = json_["RegisteredUsers"];
if (users.type() != Json::objectValue)
{
throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of users");
}
bool hasUser = false;
Json::Value::Members usernames = users.getMemberNames();
for (size_t i = 0; i < usernames.size(); i++)
{
const std::string& username = usernames[i];
std::string password = users[username].asString();
httpServer.RegisterUser(username.c_str(), password.c_str());
hasUser = true;
}
return hasUser;
}
std::string OrthancConfiguration::InterpretStringParameterAsPath(
const std::string& parameter) const
{
return SystemToolbox::InterpretRelativePath(defaultDirectory_.string(), parameter);
}
void OrthancConfiguration::GetListOfStringsParameter(std::list<std::string>& target,
const std::string& key) const
{
target.clear();
if (!json_.isMember(key))
{
return;
}
const Json::Value& lst = json_[key];
if (lst.type() != Json::arrayValue)
{
throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of strings: " + key);
}
for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
{
target.push_back(lst[i].asString());
}
}
void OrthancConfiguration::GetSetOfStringsParameter(std::set<std::string>& target,
const std::string& key) const
{
target.clear();
if (!json_.isMember(key))
{
return;
}
const Json::Value& lst = json_[key];
if (lst.type() != Json::arrayValue)
{
throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted set of strings: " + key);
}
for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
{
target.insert(lst[i].asString());
}
}
bool OrthancConfiguration::IsSameAETitle(const std::string& aet1,
const std::string& aet2) const
{
if (GetBooleanParameter("StrictAetComparison", false))
{
// Case-sensitive matching
return aet1 == aet2;
}
else
{
// Case-insensitive matching (default)
std::string tmp1, tmp2;
Toolbox::ToLowerCase(tmp1, aet1);
Toolbox::ToLowerCase(tmp2, aet2);
return tmp1 == tmp2;
}
}
bool OrthancConfiguration::LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality,
const std::string& aet) const
{
for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
{
if (IsSameAETitle(aet, it->second.GetApplicationEntityTitle()))
{
modality = it->second;
return true;
}
}
return false;
}
void OrthancConfiguration::LookupDicomModalitiesUsingAETitle(std::list<RemoteModalityParameters>& modalities,
const std::string& aet) const
{
modalities.clear();
for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
{
if (IsSameAETitle(aet, it->second.GetApplicationEntityTitle()))
{
modalities.push_back(it->second);
}
}
}
bool OrthancConfiguration::IsKnownAETitle(const std::string& aet,
const std::string& ip) const
{
RemoteModalityParameters modality;
if (!LookupDicomModalityUsingAETitle(modality, aet))
{
LOG(WARNING) << "Modality \"" << aet
<< "\" is not listed in the \"DicomModalities\" configuration option";
return false;
}
else if (!GetBooleanParameter("DicomCheckModalityHost", false) ||
ip == modality.GetHost())
{
return true;
}
else
{
LOG(WARNING) << "Forbidding access from AET \"" << aet
<< "\" given its hostname (" << ip << ") does not match "
<< "the \"DicomModalities\" configuration option ("
<< modality.GetHost() << " was expected)";
return false;
}
}
RemoteModalityParameters
OrthancConfiguration::GetModalityUsingSymbolicName(const std::string& name) const
{
RemoteModalityParameters modality;
GetDicomModalityUsingSymbolicName(modality, name);
return modality;
}
RemoteModalityParameters
OrthancConfiguration::GetModalityUsingAet(const std::string& aet) const
{
RemoteModalityParameters modality;
if (LookupDicomModalityUsingAETitle(modality, aet))
{
return modality;
}
else
{
throw OrthancException(ErrorCode_InexistentItem,
"Unknown modality for AET: " + aet);
}
}
void OrthancConfiguration::UpdateModality(const std::string& symbolicName,
const RemoteModalityParameters& modality)
{
CheckAlphanumeric(symbolicName);
modalities_[symbolicName] = modality;
SaveModalities();
}
void OrthancConfiguration::RemoveModality(const std::string& symbolicName)
{
if (modalities_.find(symbolicName) == modalities_.end())
{
throw OrthancException(ErrorCode_InexistentItem,
"Unknown DICOM modality with symbolic name: " + symbolicName);
}
else
{
modalities_.erase(symbolicName);
SaveModalities();
}
}
void OrthancConfiguration::UpdatePeer(const std::string& symbolicName,
const WebServiceParameters& peer)
{
CheckAlphanumeric(symbolicName);
peer.CheckClientCertificate();
peers_[symbolicName] = peer;
SavePeers();
}
void OrthancConfiguration::RemovePeer(const std::string& symbolicName)
{
if (peers_.find(symbolicName) == peers_.end())
{
throw OrthancException(ErrorCode_InexistentItem,
"Unknown Orthanc peer: " + symbolicName);
}
else
{
peers_.erase(symbolicName);
SavePeers();
}
}
void OrthancConfiguration::Format(std::string& result) const
{
Toolbox::WriteStyledJson(result, json_);
}
void OrthancConfiguration::SetDefaultEncoding(Encoding encoding)
{
SetDefaultDicomEncoding(encoding);
// Propagate the encoding to the configuration file that is
// stored in memory
json_["DefaultEncoding"] = EnumerationToString(encoding);
}
bool OrthancConfiguration::HasConfigurationChanged() const
{
Json::Value current;
ReadConfiguration(current, configurationFileArg_);
std::string a, b;
Toolbox::WriteFastJson(a, json_);
Toolbox::WriteFastJson(b, current);
return a != b;
}
void OrthancConfiguration::SetServerIndex(ServerIndex& index)
{
serverIndex_ = &index;
}
void OrthancConfiguration::ResetServerIndex()
{
serverIndex_ = NULL;
}
TemporaryFile* OrthancConfiguration::CreateTemporaryFile() const
{
if (json_.isMember(TEMPORARY_DIRECTORY))
{
return new TemporaryFile(InterpretStringParameterAsPath(GetStringParameter(TEMPORARY_DIRECTORY, ".")), "");
}
else
{
return new TemporaryFile;
}
}
std::string OrthancConfiguration::GetDefaultPrivateCreator() const
{
// New configuration option in Orthanc 1.6.0
return GetStringParameter("DefaultPrivateCreator", "");
}
static void GetAcceptOption(std::set<DicomTransferSyntax>& target,
const OrthancConfiguration& configuration,
const std::string& optionName,
TransferSyntaxGroup optionGroup)
{
bool accept;
if (configuration.LookupBooleanParameter(accept, optionName))
{
std::set<DicomTransferSyntax> group;
GetTransferSyntaxGroup(group, optionGroup);
for (std::set<DicomTransferSyntax>::const_iterator
syntax = group.begin(); syntax != group.end(); ++syntax)
{
if (accept)
{
target.insert(*syntax);
}
else
{
target.erase(*syntax);
}
}
}
}
void OrthancConfiguration::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target) const
{
target.clear();
#if 1
/**
* This is the behavior in Orthanc >= 1.9.0. All the transfer
* syntaxes are accepted by default, and the
* "TransferSyntaxAccepted" options can be used to disable groups
* of transfer syntaxes.
**/
static const char* const ACCEPTED_TRANSFER_SYNTAXES = "AcceptedTransferSyntaxes";
if (json_.isMember(ACCEPTED_TRANSFER_SYNTAXES))
{
ParseAcceptedTransferSyntaxes(target, json_[ACCEPTED_TRANSFER_SYNTAXES]);
}
else
{
GetAllDicomTransferSyntaxes(target);
}
#else
/**
* This was the behavior of Orthanc <= 1.8.2. The uncompressed
* transfer syntaxes were always accepted, and additional transfer
* syntaxes were added using the configuration options
* "XXXTransferSyntaxAccepted".
**/
// The 3 transfer syntaxes below were the only ones to be supported in Orthanc <= 0.7.1
target.insert(DicomTransferSyntax_LittleEndianExplicit);
target.insert(DicomTransferSyntax_BigEndianExplicit);
target.insert(DicomTransferSyntax_LittleEndianImplicit);
#endif
// Groups of transfer syntaxes, supported since Orthanc 0.7.2
GetAcceptOption(target, *this, "DeflatedTransferSyntaxAccepted", TransferSyntaxGroup_Deflated);
GetAcceptOption(target, *this, "JpegTransferSyntaxAccepted", TransferSyntaxGroup_Jpeg);
GetAcceptOption(target, *this, "Jpeg2000TransferSyntaxAccepted", TransferSyntaxGroup_Jpeg2000);
GetAcceptOption(target, *this, "JpegLosslessTransferSyntaxAccepted", TransferSyntaxGroup_JpegLossless);
GetAcceptOption(target, *this, "JpipTransferSyntaxAccepted", TransferSyntaxGroup_Jpip);
GetAcceptOption(target, *this, "Mpeg2TransferSyntaxAccepted", TransferSyntaxGroup_Mpeg2);
GetAcceptOption(target, *this, "Mpeg4TransferSyntaxAccepted", TransferSyntaxGroup_Mpeg4);
GetAcceptOption(target, *this, "RleTransferSyntaxAccepted", TransferSyntaxGroup_Rle);
GetAcceptOption(target, *this, "H265TransferSyntaxAccepted", TransferSyntaxGroup_H265);
}
std::string OrthancConfiguration::GetDatabaseServerIdentifier() const
{
std::string id;
if (LookupStringParameter(id, DATABASE_SERVER_IDENTIFIER))
{
if (id.empty())
{
throw OrthancException(ErrorCode_ParameterOutOfRange, "Global configuration option \"" +
std::string(DATABASE_SERVER_IDENTIFIER) + "\" cannot be empty");
}
else
{
return id;
}
}
else
{
std::set<std::string> items;
{
std::set<std::string> mac;
SystemToolbox::GetMacAddresses(mac);
for (std::set<std::string>::const_iterator it = mac.begin(); it != mac.end(); ++it)
{
items.insert("mac=" + *it);
}
}
items.insert("aet=" + GetStringParameter("DicomAet", "ORTHANC"));
items.insert("dicom-port=" + boost::lexical_cast<std::string>(GetUnsignedIntegerParameter("DicomPort", 4242)));
items.insert("http-port=" + boost::lexical_cast<std::string>(GetUnsignedIntegerParameter("HttpPort", 8042)));
for (std::set<std::string>::const_iterator it = items.begin(); it != items.end(); ++it)
{
if (id.empty())
{
id = *it;
}
else
{
id += ("|" + *it);
}
}
std::string hash;
Toolbox::ComputeSHA1(hash, id);
return hash;
}
}
void OrthancConfiguration::LoadWarnings()
{
if (json_.isMember(WARNINGS))
{
const Json::Value& warnings = json_[WARNINGS];
if (!warnings.isObject())
{
throw OrthancException(ErrorCode_BadFileFormat, std::string(WARNINGS) + " configuration entry is not a Json object");
}
Json::Value::Members members = warnings.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& name = members[i];
bool enabled = warnings[name].asBool();
Warnings warning = Warnings_None;
if (name == "W001_TagsBeingReadFromStorage")
{
warning = Warnings_001_TagsBeingReadFromStorage;
}
else if (name == "W002_InconsistentDicomTagsInDb")
{
warning = Warnings_002_InconsistentDicomTagsInDb;
}
else if (name == "W003_DecoderFailure")
{
warning = Warnings_003_DecoderFailure;
}
else if (name == "W004_NoMainDicomTagsSignature")
{
warning = Warnings_004_NoMainDicomTagsSignature;
}
else if (name == "W005_RequestingTagFromLowerResourceLevel")
{
warning = Warnings_005_RequestingTagFromLowerResourceLevel;
}
else if (name == "W006_RequestingTagFromMetaHeader")
{
warning = Warnings_006_RequestingTagFromMetaHeader;
}
else if (name == "W007_MissingRequestedTagsNotReadFromDisk")
{
warning = Warnings_007_MissingRequestedTagsNotReadFromDisk;
}
else
{
throw OrthancException(ErrorCode_BadFileFormat, name + " is not recognized as a valid warning name");
}
if (!enabled)
{
disabledWarnings_.insert(warning);
}
}
}
else
{
disabledWarnings_.clear();
}
}
void OrthancConfiguration::DefaultExtractDicomSummary(DicomMap& target,
const ParsedDicomFile& dicom)
{
std::set<DicomTag> ignoreTagLength;
dicom.ExtractDicomSummary(target, ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
}
void OrthancConfiguration::DefaultExtractDicomSummary(DicomMap& target,
DcmDataset& dicom)
{
std::set<DicomTag> ignoreTagLength;
FromDcmtkBridge::ExtractDicomSummary(target, dicom, ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
}
void OrthancConfiguration::DefaultDicomDatasetToJson(Json::Value& target,
const ParsedDicomFile& dicom)
{
std::set<DicomTag> ignoreTagLength;
DefaultDicomDatasetToJson(target, dicom, ignoreTagLength);
}
void OrthancConfiguration::DefaultDicomDatasetToJson(Json::Value& target,
DcmDataset& dicom,
const std::set<DicomTag>& ignoreTagLength)
{
FromDcmtkBridge::ExtractDicomAsJson(target, dicom, DicomToJsonFormat_Full, DicomToJsonFlags_Default,
ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
}
void OrthancConfiguration::DefaultDicomDatasetToJson(Json::Value& target,
const ParsedDicomFile& dicom,
const std::set<DicomTag>& ignoreTagLength)
{
dicom.DatasetToJson(target, DicomToJsonFormat_Full, DicomToJsonFlags_Default,
ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
}
void OrthancConfiguration::DefaultDicomHeaderToJson(Json::Value& target,
const ParsedDicomFile& dicom)
{
dicom.HeaderToJson(target, DicomToJsonFormat_Full);
}
static void AddTransferSyntaxes(std::set<DicomTransferSyntax>& target,
const std::string& source)
{
boost::regex pattern(Toolbox::WildcardToRegularExpression(source));
std::set<DicomTransferSyntax> allSyntaxes;
GetAllDicomTransferSyntaxes(allSyntaxes);
for (std::set<DicomTransferSyntax>::const_iterator
syntax = allSyntaxes.begin(); syntax != allSyntaxes.end(); ++syntax)
{
if (regex_match(GetTransferSyntaxUid(*syntax), pattern))
{
target.insert(*syntax);
}
}
}
void OrthancConfiguration::ParseAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target,
const Json::Value& source)
{
if (source.type() == Json::stringValue)
{
AddTransferSyntaxes(target, source.asString());
}
else if (source.type() == Json::arrayValue)
{
for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
{
if (source[i].type() == Json::stringValue)
{
AddTransferSyntaxes(target, source[i].asString());
}
else
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
}
else
{
throw OrthancException(ErrorCode_BadFileFormat);
}
}
}