changeset 6740:9551353f3e03

refactored OrthancConfiguration and OrthancRestSystem to have a single definition of keys and default values
author Alain Mazy <am@orthanc.team>
date Wed, 06 May 2026 16:55:22 +0200
parents ace9135428dd
children 7318f5f73ace
files NEWS OrthancServer/Sources/OrthancConfiguration.cpp OrthancServer/Sources/OrthancConfiguration.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/main.cpp
diffstat 7 files changed, 176 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed May 06 14:38:37 2026 +0200
+++ b/NEWS	Wed May 06 16:55:22 2026 +0200
@@ -20,6 +20,7 @@
 * New fields reported in the /system route:
   - "OverwriteInstancesMode" (note, the boolean "OverwriteInstances" field is kept for backward
     compatibility)
+  - "MaximumStorageCacheSize", "StoreMD5ForAttachments"
 * The "LocalAet" field of the payload to "/modalities/../move", "/modalities/../store", 
   "/modalities/../get", "queries/../answers/../retrieve" now always overwrites the "DicomAet"
   and the "DicomModalities.LocalAet" configurations.
--- a/OrthancServer/Sources/OrthancConfiguration.cpp	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.cpp	Wed May 06 16:55:22 2026 +0200
@@ -44,7 +44,6 @@
 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";
@@ -1143,12 +1142,12 @@
   {
     std::string id;
 
-    if (LookupStringParameter(id, DATABASE_SERVER_IDENTIFIER))
+    if (LookupStringParameter(id, ORTHANC_CONFIG_DATABASE_SERVER_IDENTIFIER))
     {
       if (id.empty())
       {
         throw OrthancException(ErrorCode_ParameterOutOfRange, "Global configuration option \"" +
-                               std::string(DATABASE_SERVER_IDENTIFIER) + "\" cannot be empty");
+                               std::string(ORTHANC_CONFIG_DATABASE_SERVER_IDENTIFIER) + "\" cannot be empty");
       }
       else
       {
@@ -1169,9 +1168,9 @@
         }
       }
 
-      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)));
+      items.insert("aet=" + GetOrthancAET());
+      items.insert("dicom-port=" + boost::lexical_cast<std::string>(GetDicomPort()));
+      items.insert("http-port=" + boost::lexical_cast<std::string>(GetHttpPort()));
 
       for (std::set<std::string>::const_iterator it = items.begin(); it != items.end(); ++it)
       {
--- a/OrthancServer/Sources/OrthancConfiguration.h	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/OrthancConfiguration.h	Wed May 06 16:55:22 2026 +0200
@@ -37,6 +37,25 @@
 
 class DcmDataset;
 
+#define ORTHANC_CONFIG_NAME "Name"
+#define ORTHANC_CONFIG_DICOM_AET "DicomAet"
+#define ORTHANC_CONFIG_DICOM_PORT "DicomPort"
+#define ORTHANC_CONFIG_HTTP_PORT "HttpPort"
+#define ORTHANC_CONFIG_MAXIMUM_STORAGE_CACHE_SIZE "MaximumStorageCacheSize"
+#define ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE "MaximumStorageSize"
+#define ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE "MaximumStorageMode"
+#define ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT "MaximumPatientCount"
+#define ORTHANC_CONFIG_CHECK_REVISIONS "CheckRevisions"
+#define ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS "StoreMD5ForAttachments"
+#define ORTHANC_CONFIG_STORAGE_COMPRESSION "StorageCompression"
+#define ORTHANC_CONFIG_OVERWRITE_INSTANCES "OverwriteInstances"
+#define ORTHANC_CONFIG_INGEST_TRANSCODING "IngestTranscoding"
+#define ORTHANC_CONFIG_DATABASE_SERVER_IDENTIFIER "DatabaseServerIdentifier"
+#define ORTHANC_CONFIG_DICOM_DEFAULT_RETRIEVE_METHOD "DicomDefaultRetrieveMethod"
+#define ORTHANC_CONFIG_PATIENT_LEVEL_ENABLED "PatientLevelEnabled"
+#define ORTHANC_CONFIG_READ_ONLY "ReadOnly"
+
+
 namespace Orthanc
 {
   class DicomMap;
@@ -264,11 +283,6 @@
 
     std::string GetDefaultPrivateCreator() const;
 
-    std::string GetOrthancAET() const
-    {
-      return GetStringParameter("DicomAet", "ORTHANC");
-    }
-
     void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target) const;
 
     std::string GetDatabaseServerIdentifier() const;
@@ -278,6 +292,75 @@
       return disabledWarnings_.count(warning) == 0;
     }
 
+    std::string GetOrthancAET() const
+    {
+      return GetStringParameter(ORTHANC_CONFIG_DICOM_AET, "ORTHANC");
+    }
+
+    std::string GetOrthancName() const
+    {
+      return GetStringParameter(ORTHANC_CONFIG_NAME, "ORTHANC");
+    }
+
+    std::string GetIngestTranscoding() const
+    {
+      return GetStringParameter(ORTHANC_CONFIG_INGEST_TRANSCODING, "");
+    }
+
+    std::string GetMaximumStorageMode() const
+    {
+      return GetStringParameter(ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE, "Recycle");
+    }
+
+    std::string GetDicomDefaultRetrieveMethod() const
+    {
+      return GetStringParameter(ORTHANC_CONFIG_DICOM_DEFAULT_RETRIEVE_METHOD, "C-MOVE");
+    }
+    unsigned int GetMaximumStorageCacheSize() const
+    {
+      return GetUnsignedIntegerParameter(ORTHANC_CONFIG_MAXIMUM_STORAGE_CACHE_SIZE, 128);
+    }
+
+    unsigned int GetMaximumStorageSize() const
+    {
+      return GetUnsignedIntegerParameter(ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE, 0);
+    }
+
+    unsigned int GetMaximumPatientCount() const
+    {
+      return GetUnsignedIntegerParameter(ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT, 0);
+    }
+    
+    unsigned int GetDicomPort() const
+    {
+      return GetUnsignedIntegerParameter(ORTHANC_CONFIG_DICOM_PORT, 4242);
+    }
+
+    unsigned int GetHttpPort() const
+    {
+      return GetUnsignedIntegerParameter(ORTHANC_CONFIG_HTTP_PORT, 8042);
+    }
+
+    bool HasCheckRevisions() const
+    {
+      return GetBooleanParameter(ORTHANC_CONFIG_CHECK_REVISIONS, false);
+    }
+
+    bool HasStoreMD5ForAttachments() const
+    {
+      return GetBooleanParameter(ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS, true);
+    }
+
+    bool HasStorageCompression() const
+    {
+      return GetBooleanParameter(ORTHANC_CONFIG_STORAGE_COMPRESSION, false);
+    }
+
+    bool HasPatientLevelEnabled() const
+    {
+      return GetBooleanParameter(ORTHANC_CONFIG_PATIENT_LEVEL_ENABLED, true);
+    }
+
     static void DefaultExtractDicomSummary(DicomMap& target,
                                            const ParsedDicomFile& dicom);
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed May 06 16:55:22 2026 +0200
@@ -60,8 +60,6 @@
 static Orthanc::Semaphore throttlingSemaphore_(4);  // TODO => PARAMETER?
 
 
-static const std::string CHECK_REVISIONS = "CheckRevisions";
-
 static const char* const IGNORE_LENGTH = "ignore-length";
 static const char* const RECONSTRUCT_FILES = "ReconstructFiles";
 static const char* const LIMIT_TO_THIS_LEVEL_MAIN_DICOM_TAGS = "LimitToThisLevelMainDicomTags";
@@ -1949,7 +1947,7 @@
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)")
         .SetHttpHeader("If-Match", "Revision of the metadata, to check if its content has not changed and can "
-                       "be deleted. This header is mandatory if `CheckRevisions` option is `true`.");
+                       "be deleted. This header is mandatory if `" + std::string(ORTHANC_CONFIG_CHECK_REVISIONS) + "` option is `true`.");
       return;
     }
 
@@ -1971,10 +1969,10 @@
       else
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
+        if (lock.GetConfiguration().HasCheckRevisions())
         {
           throw OrthancException(ErrorCode_Revision,
-                                 "HTTP header \"If-Match\" is missing, as \"CheckRevisions\" is \"true\"");
+                                 "HTTP header \"If-Match\" is missing, as \"" + std::string(ORTHANC_CONFIG_CHECK_REVISIONS) + "\" is \"true\"");
         }
         else
         {
@@ -2034,7 +2032,7 @@
       if (!hasOldRevision)
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
+        if (lock.GetConfiguration().HasCheckRevisions())
         {
           // "StatelessDatabaseOperations::SetMetadata()" will ignore
           // the actual value of "oldRevision" if the metadata is
@@ -2654,7 +2652,7 @@
       if (!hasOldRevision)
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
+        if (lock.GetConfiguration().HasCheckRevisions())
         {
           // "StatelessDatabaseOperations::AddAttachment()" will ignore
           // the actual value of "oldRevision" if the metadata is
@@ -2738,10 +2736,10 @@
       else
       {
         OrthancConfiguration::ReaderLock lock;
-        if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
+        if (lock.GetConfiguration().HasCheckRevisions())
         {
           throw OrthancException(ErrorCode_Revision,
-                                 "HTTP header \"If-Match\" is missing, as \"CheckRevisions\" is \"true\"");
+                                 "HTTP header \"If-Match\" is missing, as \"" + std::string(ORTHANC_CONFIG_CHECK_REVISIONS) + "\" is \"true\"");
         }
         else
         {
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Wed May 06 16:55:22 2026 +0200
@@ -71,27 +71,14 @@
   static void GetSystemInformation(RestApiGetCall& call)
   {
     static const char* const API_VERSION = "ApiVersion";
-    static const char* const CHECK_REVISIONS = "CheckRevisions";
     static const char* const DATABASE_BACKEND_PLUGIN = "DatabaseBackendPlugin";
     static const char* const DATABASE_VERSION = "DatabaseVersion";
-    static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier";
-    static const char* const DICOM_DEFAULT_RETRIEVE_METHOD = "DicomDefaultRetrieveMethod";
-    static const char* const DICOM_AET = "DicomAet";
-    static const char* const DICOM_PORT = "DicomPort";
-    static const char* const HTTP_PORT = "HttpPort";
     static const char* const IS_HTTP_SERVER_SECURE = "IsHttpServerSecure";
-    static const char* const NAME = "Name";
     static const char* const PLUGINS_ENABLED = "PluginsEnabled";
     static const char* const STORAGE_AREA_PLUGIN = "StorageAreaPlugin";
     static const char* const VERSION = "Version";
     static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
-    static const char* const STORAGE_COMPRESSION = "StorageCompression";
-    static const char* const OVERWRITE_INSTANCES = "OverwriteInstances";
     static const char* const OVERWRITE_INSTANCES_MODE = "OverwriteInstancesMode";
-    static const char* const INGEST_TRANSCODING = "IngestTranscoding";
-    static const char* const MAXIMUM_STORAGE_SIZE = "MaximumStorageSize";
-    static const char* const MAXIMUM_PATIENT_COUNT = "MaximumPatientCount";
-    static const char* const MAXIMUM_STORAGE_MODE = "MaximumStorageMode";
     static const char* const USER_METADATA = "UserMetadata";
     static const char* const HAS_LABELS = "HasLabels";
     static const char* const CAPABILITIES = "Capabilities";
@@ -99,9 +86,7 @@
     static const char* const HAS_KEY_VALUE_STORES = "HasKeyValueStores";
     static const char* const HAS_QUEUES = "HasQueues";
     static const char* const HAS_EXTENDED_FIND = "HasExtendedFind";
-    static const char* const READ_ONLY = "ReadOnly";
     static const char* const HAS_RESERVE_QUEUE_VALUE = "HasReserveQueueValue";
-    static const char* const PATIENT_LEVEL_ENABLED = "PatientLevelEnabled";
 
     if (call.IsDocumentation())
     {
@@ -113,7 +98,7 @@
         .SetAnswerField(VERSION, RestApiCallDocumentation::Type_String, "Version of Orthanc")
         .SetAnswerField(DATABASE_VERSION, RestApiCallDocumentation::Type_Number,
                         "Version of the database: https://orthanc.uclouvain.be/book/developers/db-versioning.html")
-        .SetAnswerField(DATABASE_SERVER_IDENTIFIER, RestApiCallDocumentation::Type_String,
+        .SetAnswerField(ORTHANC_CONFIG_DATABASE_SERVER_IDENTIFIER, RestApiCallDocumentation::Type_String,
                         "ID of the server in the database (when running multiple Orthanc on the same DB)")
         .SetAnswerField(IS_HTTP_SERVER_SECURE, RestApiCallDocumentation::Type_Boolean,
                         "Whether the REST API is properly secured (assuming no reverse proxy is in use): https://orthanc.uclouvain.be/book/faq/security.html#securing-the-http-server")
@@ -121,32 +106,36 @@
                         "Information about the installed storage area plugin (`null` if no such plugin is installed)")
         .SetAnswerField(DATABASE_BACKEND_PLUGIN, RestApiCallDocumentation::Type_String,
                         "Information about the installed database index plugin (`null` if no such plugin is installed)")
-        .SetAnswerField(DICOM_DEFAULT_RETRIEVE_METHOD, RestApiCallDocumentation::Type_String, "The DicomDefaultRetrieveMethod configuration")
-        .SetAnswerField(DICOM_AET, RestApiCallDocumentation::Type_String, "The DICOM AET of Orthanc")
-        .SetAnswerField(DICOM_PORT, RestApiCallDocumentation::Type_Number, "The port to the DICOM server of Orthanc")
-        .SetAnswerField(HTTP_PORT, RestApiCallDocumentation::Type_Number, "The port to the HTTP server of Orthanc")
-        .SetAnswerField(NAME, RestApiCallDocumentation::Type_String,
-                        "The name of the Orthanc server, cf. the `Name` configuration option")
+        .SetAnswerField(ORTHANC_CONFIG_DICOM_DEFAULT_RETRIEVE_METHOD, RestApiCallDocumentation::Type_String, "The " + std::string(ORTHANC_CONFIG_DICOM_DEFAULT_RETRIEVE_METHOD) + " configuration")
+        .SetAnswerField(ORTHANC_CONFIG_DICOM_AET, RestApiCallDocumentation::Type_String, "The DICOM AET of Orthanc")
+        .SetAnswerField(ORTHANC_CONFIG_DICOM_PORT, RestApiCallDocumentation::Type_Number, "The port to the DICOM server of Orthanc")
+        .SetAnswerField(ORTHANC_CONFIG_HTTP_PORT, RestApiCallDocumentation::Type_Number, "The port to the HTTP server of Orthanc")
+        .SetAnswerField(ORTHANC_CONFIG_NAME, RestApiCallDocumentation::Type_String,
+                        "The name of the Orthanc server, cf. the `" + std::string(ORTHANC_CONFIG_NAME) + "` configuration option")
         .SetAnswerField(PLUGINS_ENABLED, RestApiCallDocumentation::Type_Boolean,
                         "Whether Orthanc was built with support for plugins")
-        .SetAnswerField(CHECK_REVISIONS, RestApiCallDocumentation::Type_Boolean,
+        .SetAnswerField(ORTHANC_CONFIG_CHECK_REVISIONS, RestApiCallDocumentation::Type_Boolean,
                         "Whether Orthanc handle revisions of metadata and attachments to deal with multiple writers (new in Orthanc 1.9.2)")
         .SetAnswerField(MAIN_DICOM_TAGS, RestApiCallDocumentation::Type_JsonObject,
                         "The list of MainDicomTags saved in DB for each resource level (new in Orthanc 1.11.0)")
-        .SetAnswerField(STORAGE_COMPRESSION, RestApiCallDocumentation::Type_Boolean,
+        .SetAnswerField(ORTHANC_CONFIG_STORAGE_COMPRESSION, RestApiCallDocumentation::Type_Boolean,
                         "Whether storage compression is enabled (new in Orthanc 1.11.0)")
-        .SetAnswerField(OVERWRITE_INSTANCES, RestApiCallDocumentation::Type_Boolean,
+        .SetAnswerField(ORTHANC_CONFIG_OVERWRITE_INSTANCES, RestApiCallDocumentation::Type_Boolean,
                         "Whether instances are overwritten when re-ingested (new in Orthanc 1.11.0 and kept as a bool for backward compatibility)")
         .SetAnswerField(OVERWRITE_INSTANCES_MODE, RestApiCallDocumentation::Type_String,
                         "Whether instances are overwritten when re-ingested (new in Orthanc 1.12.12)")
-        .SetAnswerField(INGEST_TRANSCODING, RestApiCallDocumentation::Type_String,
+        .SetAnswerField(ORTHANC_CONFIG_INGEST_TRANSCODING, RestApiCallDocumentation::Type_String,
                         "Whether instances are transcoded when ingested into Orthanc (`""` if no transcoding is performed) (new in Orthanc 1.11.0)")
-        .SetAnswerField(MAXIMUM_STORAGE_SIZE, RestApiCallDocumentation::Type_Number,
-                        "The configured MaximumStorageSize in MB (new in Orthanc 1.11.3)")
-        .SetAnswerField(MAXIMUM_PATIENT_COUNT, RestApiCallDocumentation::Type_Number,
-                        "The configured MaximumPatientCount (new in Orthanc 1.12.4)")
-        .SetAnswerField(MAXIMUM_STORAGE_MODE, RestApiCallDocumentation::Type_String,
-                        "The configured MaximumStorageMode (new in Orthanc 1.11.3)")
+        .SetAnswerField(ORTHANC_CONFIG_MAXIMUM_STORAGE_CACHE_SIZE, RestApiCallDocumentation::Type_Number,
+                        std::string("The configured ") + ORTHANC_CONFIG_MAXIMUM_STORAGE_CACHE_SIZE + " in MB (new in Orthanc 1.12.12)")
+        .SetAnswerField(ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS, RestApiCallDocumentation::Type_Boolean,
+                        std::string("The configured ") + ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS + " (new in Orthanc 1.12.12)")
+        .SetAnswerField(ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE, RestApiCallDocumentation::Type_Number,
+                        "The configured " + std::string(ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE) + " in MB (new in Orthanc 1.11.3)")
+        .SetAnswerField(ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT, RestApiCallDocumentation::Type_Number,
+                        "The configured " + std::string(ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT) + " (new in Orthanc 1.12.4)")
+        .SetAnswerField(ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE, RestApiCallDocumentation::Type_String,
+                        "The configured " + std::string(ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE) + " (new in Orthanc 1.11.3)")
         .SetAnswerField(USER_METADATA, RestApiCallDocumentation::Type_JsonObject,
                         "The configured UserMetadata (new in Orthanc 1.12.0)")
         .SetAnswerField(HAS_LABELS, RestApiCallDocumentation::Type_Boolean,
@@ -155,9 +144,9 @@
                         "Whether the database back-end supports optional features like 'HasExtendedChanges', 'HasExtendedFind' "
                         "(new in Orthanc 1.12.5), 'HasKeyValueStores', 'HasQueues' (new in Orthanc 1.12.8), "
                         "and 'HasReserveQueueValue' (new in Orthanc 1.12.10)")
-        .SetAnswerField(READ_ONLY, RestApiCallDocumentation::Type_Boolean,
+        .SetAnswerField(ORTHANC_CONFIG_READ_ONLY, RestApiCallDocumentation::Type_Boolean,
                         "Whether Orthanc is running in read only mode (new in Orthanc 1.12.5)")
-        .SetAnswerField(PATIENT_LEVEL_ENABLED, RestApiCallDocumentation::Type_Boolean,
+        .SetAnswerField(ORTHANC_CONFIG_PATIENT_LEVEL_ENABLED, RestApiCallDocumentation::Type_Boolean,
                         "Whether Patient level routes and sanity checks are enabled (new in Orthanc 1.12.11)")
         .SetHttpGetSample("https://orthanc.uclouvain.be/demo/system", true);
       return;
@@ -174,26 +163,29 @@
 
     {
       OrthancConfiguration::ReaderLock lock;
-      result[DICOM_AET] = lock.GetConfiguration().GetOrthancAET();
-      result[DICOM_PORT] = lock.GetConfiguration().GetUnsignedIntegerParameter(DICOM_PORT, 4242);
-      result[HTTP_PORT] = lock.GetConfiguration().GetUnsignedIntegerParameter(HTTP_PORT, 8042);
-      result[NAME] = lock.GetConfiguration().GetStringParameter(NAME, "");
-      result[CHECK_REVISIONS] = lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false);  // New in Orthanc 1.9.2
-      result[STORAGE_COMPRESSION] = lock.GetConfiguration().GetBooleanParameter(STORAGE_COMPRESSION, false); // New in Orthanc 1.11.0
-      result[OVERWRITE_INSTANCES] = context.IsOverwriteInstances(); // New in Orthanc 1.11.0
-      result[OVERWRITE_INSTANCES_MODE] = EnumerationToString(context.GetOverwriteInstances()); // New in Orthanc 1.12.12
-      result[INGEST_TRANSCODING] = lock.GetConfiguration().GetStringParameter(INGEST_TRANSCODING, ""); // New in Orthanc 1.11.0
-      result[DATABASE_SERVER_IDENTIFIER] = lock.GetConfiguration().GetDatabaseServerIdentifier();
-      result[MAXIMUM_STORAGE_SIZE] = lock.GetConfiguration().GetUnsignedIntegerParameter(MAXIMUM_STORAGE_SIZE, 0); // New in Orthanc 1.11.3
-      result[MAXIMUM_PATIENT_COUNT] = lock.GetConfiguration().GetUnsignedIntegerParameter(MAXIMUM_PATIENT_COUNT, 0); // New in Orthanc 1.12.4
-      result[MAXIMUM_STORAGE_MODE] = lock.GetConfiguration().GetStringParameter(MAXIMUM_STORAGE_MODE, "Recycle"); // New in Orthanc 1.11.3
-      result[DICOM_DEFAULT_RETRIEVE_METHOD] = lock.GetConfiguration().GetStringParameter(DICOM_DEFAULT_RETRIEVE_METHOD, "C-MOVE");
+      result[ORTHANC_CONFIG_NAME] = lock.GetConfiguration().GetOrthancName();
+      result[ORTHANC_CONFIG_DICOM_AET] = lock.GetConfiguration().GetOrthancAET();
+      result[ORTHANC_CONFIG_DICOM_PORT] = lock.GetConfiguration().GetDicomPort();
+      result[ORTHANC_CONFIG_HTTP_PORT] = lock.GetConfiguration().GetHttpPort();
+      result[ORTHANC_CONFIG_CHECK_REVISIONS] = lock.GetConfiguration().HasCheckRevisions();  // New in Orthanc 1.9.2
+      result[ORTHANC_CONFIG_MAXIMUM_STORAGE_CACHE_SIZE] = lock.GetConfiguration().GetMaximumStorageCacheSize(); // New in Orthanc 1.12.12
+      result[ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS] = lock.GetConfiguration().HasStoreMD5ForAttachments(); // New in Orthanc 1.12.12
+      result[ORTHANC_CONFIG_STORAGE_COMPRESSION] = lock.GetConfiguration().HasStorageCompression(); // New in Orthanc 1.11.0
+      result[ORTHANC_CONFIG_INGEST_TRANSCODING] = lock.GetConfiguration().GetIngestTranscoding(); // New in Orthanc 1.11.0
+      result[ORTHANC_CONFIG_DATABASE_SERVER_IDENTIFIER] = lock.GetConfiguration().GetDatabaseServerIdentifier();
+      result[ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE] = lock.GetConfiguration().GetMaximumStorageSize(); // New in Orthanc 1.11.3
+      result[ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT] = lock.GetConfiguration().GetMaximumPatientCount(); // New in Orthanc 1.12.4
+      result[ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE] = lock.GetConfiguration().GetMaximumStorageMode(); // New in Orthanc 1.11.3
+      result[ORTHANC_CONFIG_DICOM_DEFAULT_RETRIEVE_METHOD] = lock.GetConfiguration().GetDicomDefaultRetrieveMethod();
     }
 
+    result[ORTHANC_CONFIG_OVERWRITE_INSTANCES] = context.IsOverwriteInstances(); // New in Orthanc 1.11.0
+    result[OVERWRITE_INSTANCES_MODE] = EnumerationToString(context.GetOverwriteInstances()); // New in Orthanc 1.12.12
+    result[ORTHANC_CONFIG_PATIENT_LEVEL_ENABLED] = context.IsPatientLevelEnabled(); // New in Orthanc 1.12.11
+
     result[STORAGE_AREA_PLUGIN] = Json::nullValue;
     result[DATABASE_BACKEND_PLUGIN] = Json::nullValue;
-    result[READ_ONLY] = context.IsReadOnly();
-    result[PATIENT_LEVEL_ENABLED] = context.IsPatientLevelEnabled();
+    result[ORTHANC_CONFIG_READ_ONLY] = context.IsReadOnly();
 
 #if ORTHANC_ENABLE_PLUGINS == 1
     result[PLUGINS_ENABLED] = true;
--- a/OrthancServer/Sources/ServerContext.cpp	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Wed May 06 16:55:22 2026 +0200
@@ -440,7 +440,7 @@
         builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After"));
 
         std::string s;
-        if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding"))
+        if (lock.GetConfiguration().LookupStringParameter(s, ORTHANC_CONFIG_INGEST_TRANSCODING))
         {
           if (LookupTransferSyntax(ingestTransferSyntax_, s))
           {
@@ -533,7 +533,7 @@
 
         SetAcceptedSopClasses(acceptedSopClasses, rejectedSopClasses);
 
-        defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetStringParameter("DicomDefaultRetrieveMethod", "C-MOVE"));
+        defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetDicomDefaultRetrieveMethod());
 
         dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetDefaultLossyQuality(lock.GetConfiguration().GetDicomLossyTranscodingQuality());
       }
--- a/OrthancServer/Sources/main.cpp	Wed May 06 14:38:37 2026 +0200
+++ b/OrthancServer/Sources/main.cpp	Wed May 06 16:55:22 2026 +0200
@@ -71,7 +71,6 @@
 static const char* const KEY_DICOM_TLS_MINIMUM_PROTOCOL_VERSION = "DicomTlsMinimumProtocolVersion";
 static const char* const KEY_DICOM_TLS_ACCEPTED_CIPHERS = "DicomTlsCiphersAccepted";
 static const char* const KEY_MAXIMUM_PDU_LENGTH = "MaximumPduLength";
-static const char* const KEY_READ_ONLY = "ReadOnly";
 static const char* const KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS = "MaximumConcurrentDcmtkTranscoders";
 
 
@@ -1117,7 +1116,7 @@
   
       // HTTP server
       httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 50));
-      httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042));
+      httpServer.SetPortNumber(lock.GetConfiguration().GetHttpPort());
       std::set<std::string> httpBindAddresses;
       lock.GetConfiguration().GetSetOfStringsParameter(httpBindAddresses, "HttpBindAddresses");
       httpServer.SetBindAddresses(httpBindAddresses);
@@ -1394,7 +1393,7 @@
       OrthancConfiguration::ReaderLock lock;
       dicomServer.SetCalledApplicationEntityTitleCheck(lock.GetConfiguration().GetBooleanParameter("DicomCheckCalledAet", false));
       dicomServer.SetAssociationTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScpTimeout", 30));
-      dicomServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242));
+      dicomServer.SetPortNumber(lock.GetConfiguration().GetDicomPort());
       dicomServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomThreadsCount", 4));
       dicomServer.SetApplicationEntityTitle(lock.GetConfiguration().GetOrthancAET());
 
@@ -1680,7 +1679,7 @@
     }
 
     // New option in Orthanc 1.12.5
-    readOnly = lock.GetConfiguration().GetBooleanParameter(KEY_READ_ONLY, false);
+    readOnly = lock.GetConfiguration().GetBooleanParameter(ORTHANC_CONFIG_READ_ONLY, false);
     
     // New option in Orthanc 1.12.6
     maxDcmtkConcurrentTranscoders = lock.GetConfiguration().GetUnsignedIntegerParameter(KEY_MAXIMUM_CONCURRENT_DCMTK_TRANSCODERS, 0);
@@ -1708,43 +1707,43 @@
   {
     OrthancConfiguration::ReaderLock lock;
 
-    context.SetPatientLevelEnabled(lock.GetConfiguration().GetBooleanParameter("PatientLevelEnabled", true));
+    context.SetPatientLevelEnabled(lock.GetConfiguration().HasPatientLevelEnabled());
 
     if (context.IsReadOnly())
     {
-      LOG(WARNING) << "READ-ONLY SYSTEM: ignoring these configurations: StorageCompression, StoreMD5ForAttachments, OverwriteInstances, MaximumPatientCount, MaximumStorageSize, MaximumStorageMode, SaveJobs"; 
+      LOG(WARNING) << "READ-ONLY SYSTEM: ignoring these configurations: " << ORTHANC_CONFIG_STORAGE_COMPRESSION << ", " << ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS << ", " << ORTHANC_CONFIG_OVERWRITE_INSTANCES << ", " << ORTHANC_CONFIG_MAXIMUM_PATIENT_COUNT << ", " << ORTHANC_CONFIG_MAXIMUM_STORAGE_SIZE <<", " << ORTHANC_CONFIG_MAXIMUM_STORAGE_MODE << ", SaveJobs"; 
     }
     else
     {
-      context.SetCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("StorageCompression", false));
-      context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true));
+      context.SetCompressionEnabled(lock.GetConfiguration().HasStorageCompression());
+      context.SetStoreMD5ForAttachments(lock.GetConfiguration().HasStoreMD5ForAttachments());
 
       // New option in Orthanc 1.4.2 (bool), changed to a string in 1.12.12
       OverwriteInstancesMode overwriteInstancesMode = OverwriteInstancesMode_Never;
-      if (lock.GetJson().isMember("OverwriteInstances"))
+      if (lock.GetJson().isMember(ORTHANC_CONFIG_OVERWRITE_INSTANCES))
       {
         std::string strOverwriteInstancesMode;
 
-        if (lock.GetJson()["OverwriteInstances"].isString() &&
-          lock.GetConfiguration().LookupStringParameter(strOverwriteInstancesMode, "OverwriteInstances"))
+        if (lock.GetJson()[ORTHANC_CONFIG_OVERWRITE_INSTANCES].isString() &&
+          lock.GetConfiguration().LookupStringParameter(strOverwriteInstancesMode, ORTHANC_CONFIG_OVERWRITE_INSTANCES))
         {
           overwriteInstancesMode = StringToOverwriteInstancesMode(strOverwriteInstancesMode);
           if (overwriteInstancesMode == OverwriteInstancesMode_IfChanged && !context.IsStoreMD5ForAttachments())
           {
-            LOG(ERROR) << "Can not set \"OverwriteInstances\" to \"IfChanged\" when \"StoreMD5ForAttachments\" is set to false.";
+            LOG(ERROR) << "Can not set \"" << ORTHANC_CONFIG_OVERWRITE_INSTANCES << "\" to \"IfChanged\" when \"" << ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS << "\" is set to false.";
             return false;
           }
         }
-        else if (lock.GetJson()["OverwriteInstances"].isBool())
+        else if (lock.GetJson()[ORTHANC_CONFIG_OVERWRITE_INSTANCES].isBool())
         {
-          bool overwriteInstancesLegacy = lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false);
+          bool overwriteInstancesLegacy = lock.GetConfiguration().GetBooleanParameter(ORTHANC_CONFIG_OVERWRITE_INSTANCES, false);
           overwriteInstancesMode = (overwriteInstancesLegacy ? OverwriteInstancesMode_Always : OverwriteInstancesMode_Never);
         }
       }
       context.SetOverwriteInstances(overwriteInstancesMode);
 
-      unsigned int maximumPatientCount = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0);
-      unsigned int maximumStorageSize = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0);
+      unsigned int maximumPatientCount = lock.GetConfiguration().GetMaximumPatientCount();
+      unsigned int maximumStorageSize = lock.GetConfiguration().GetMaximumStorageSize();
 
       if (!context.IsPatientLevelEnabled() && (maximumPatientCount != 0 || maximumStorageSize != 0))
       {
@@ -1773,7 +1772,7 @@
 
       try
       {
-        std::string mode = lock.GetConfiguration().GetStringParameter("MaximumStorageMode", "Recycle");
+        std::string mode = lock.GetConfiguration().GetMaximumStorageMode();
         context.GetIndex().SetMaximumStorageMode(StringToMaxStorageMode(mode));
       }
       catch (...)
@@ -1785,7 +1784,16 @@
     // note: this config is valid in ReadOnlyMode
     try
     {
-      uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageCacheSize", 128);
+      uint64_t size = lock.GetConfiguration().GetMaximumStorageCacheSize();
+      if (size == 0)
+      {
+        LOG(WARNING) << "Storage cache is disabled";
+      }
+      else
+      {
+        LOG(WARNING) << "Storage cache size is " << size << " MB";
+      }
+      
       context.SetMaximumStorageCacheSize(size * 1024 * 1024);
     }
     catch (...)
@@ -1833,11 +1841,9 @@
   }
 
   {
-    static const char* const CHECK_REVISIONS = "CheckRevisions";
-    
     OrthancConfiguration::ReaderLock lock;
     
-    if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
+    if (lock.GetConfiguration().HasCheckRevisions())
     {
       if (database.GetDatabaseCapabilities().HasRevisionsSupport())
       {
@@ -1847,17 +1853,15 @@
       else
       {
         LOG(WARNING) << "The custom database back-end has *no* support for revisions of metadata and attachments, "
-                     << "but configuration option \"" << CHECK_REVISIONS << "\" is set to \"true\"";
+                     << "but configuration option \"" << ORTHANC_CONFIG_CHECK_REVISIONS << "\" is set to \"true\"";
       }
       
-      static const char* const STORE_MD5 = "StoreMD5ForAttachments";
-
-      if (!lock.GetConfiguration().GetBooleanParameter(STORE_MD5, true))
+      if (!lock.GetConfiguration().HasStoreMD5ForAttachments())
       {
         throw OrthancException(
           ErrorCode_ParameterOutOfRange,
-          "The revision system is enabled by configuration option \"" + std::string(CHECK_REVISIONS) +
-          "\", but won't work properly for attachments if \"" + std::string(STORE_MD5) + "\" is set to \"false\"");
+          "The revision system is enabled by configuration option \"" + std::string(ORTHANC_CONFIG_CHECK_REVISIONS) +
+          "\", but won't work properly for attachments if \"" + std::string(ORTHANC_CONFIG_STORE_MD5_FOR_ATTACHMENTS) + "\" is set to \"false\"");
       }
     }
   }