changeset 6735:8594adfe25e2

Fix and standardize usage of LocalAet
author Alain Mazy <am@orthanc.team>
date Mon, 27 Apr 2026 16:02:50 +0200
parents cba457078ce8
children f3ef0c4ee0b9
files NEWS OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp OrthancServer/Sources/OrthancMoveRequestHandler.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Sources/QueryRetrieveHandler.cpp OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp
diffstat 7 files changed, 63 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Apr 22 13:09:28 2026 +0200
+++ b/NEWS	Mon Apr 27 16:02:50 2026 +0200
@@ -17,9 +17,12 @@
 --------
 
 * API version upgraded to 31
-* new fields reported in the /system route:
+* New fields reported in the /system route:
   - "OverwriteInstancesMode" (note, the boolean "OverwriteInstances" field is kept for backward
     compatibility)
+* 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.
 
 
 Maintenance
@@ -29,6 +32,7 @@
   (bug introduced in 1.12.10)
 * Fix OrthancPeerStore jobs that was using the loader threads only if transcoding
   was required.
+* Fix usage of "LocalAet" in C-Find and "queries/../answers/../retrieve".
 * Fix Orthanc::ImageAccessor that was broken in Orthanc Framework 1.12.11
 
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -265,10 +265,6 @@
     }
 
     std::string localAet = parameters.GetLocalApplicationEntityTitle();
-    if (parameters.GetRemoteModality().HasLocalAet())
-    {
-      localAet = parameters.GetRemoteModality().GetLocalAet();
-    }
 
     CLOG(INFO, DICOM) << "Opening a DICOM SCU connection "
                       << (parameters.GetRemoteModality().IsDicomTlsEnabled() ? "using DICOM TLS" : "without DICOM TLS")
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -46,7 +46,7 @@
     {
     private:
       ServerContext& context_;
-      const std::string& localAet_;
+      std::string localAet_;
       std::vector<std::string> instancesIds_;
       // std::vector<FileInfo> filesInfo_;
       std::unique_ptr<ThreadedInstancesLoader> instancesLoader_;
@@ -64,7 +64,7 @@
                       const std::string& originatorAet,
                       uint16_t originatorId) :
         context_(context),
-        localAet_(context.GetDefaultLocalApplicationEntityTitle()),
+        localAet_(context.GetDefaultLocalApplicationEntityTitle()),  // from the global config
         position_(0),
         originatorAet_(originatorAet),
         originatorId_(originatorId)
@@ -77,6 +77,11 @@
           loaderThreads = lock.GetConfiguration().GetLoaderThreads();
         }
 
+        if (remote_.HasLocalAet())
+        {
+          localAet_ = remote_.GetLocalAet();  // from the DicomModalities config
+        }
+
         instancesLoader_.reset(new ThreadedInstancesLoader(context_, loaderThreads, false, DicomTransferSyntax_BigEndianExplicit /* dummy value*/, 0, "CSTO"));
 
         std::vector<FileInfo> filesInfo;
@@ -150,13 +155,21 @@
         job_->SetDescription("C-MOVE");
         //job_->SetPermissive(true);  // This was the behavior of Orthanc < 1.6.0
         job_->SetPermissive(false);
-        job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle());
+
+        std::string localAet = context.GetDefaultLocalApplicationEntityTitle(); // from the global configuration
 
         {
           OrthancConfiguration::ReaderLock lock;
-          job_->SetRemoteModality(lock.GetConfiguration().GetModalityUsingAet(targetAet));
+          Orthanc::RemoteModalityParameters remoteModality = lock.GetConfiguration().GetModalityUsingAet(targetAet);
+          job_->SetRemoteModality(remoteModality);
+          if (remoteModality.HasLocalAet())
+          {
+            localAet = remoteModality.GetLocalAet(); // from the DicomModalities config
+          }
         }
 
+        job_->SetLocalAet(localAet);
+
         if (originatorId != 0)
         {
           job_->SetMoveOriginator(originatorAet, originatorId);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -681,8 +681,7 @@
                          "Whether to normalize the query, i.e. whether to wipe out from the query, the DICOM tags "
                          "that are not applicable for the query-retrieve level of interest", false)
         .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String,
-                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
-                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option or `LocalAet` in the `DicomModalities`. ", false)
         .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number,
                          "Timeout for the C-FIND command and subsequent C-GET/C-MOVE retrievals, in seconds (new in Orthanc 1.9.1).  Orthanc will close the related DICOM associations if no DICOM messages are received within this time period.", false)
         .SetAnswerField("ID", RestApiCallDocumentation::Type_String,
@@ -920,20 +919,26 @@
                                 size_t index)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
-
-    std::string targetAet;
+    QueryAccessor query(call);
+    RemoteModalityParameters remoteModality = query.GetHandler().GetRemoteModality();
+
+    std::string targetAet = context.GetDefaultLocalApplicationEntityTitle(); // from the global configuration
+    if (remoteModality.HasLocalAet())
+    {
+      targetAet = remoteModality.GetLocalAet();
+    }
+
     int timeout = -1;
 
-    QueryAccessor query(call);
-
-    RetrieveMethod retrieveMethod = query.GetHandler().GetRemoteModality().GetRetrieveMethod();
+    RetrieveMethod retrieveMethod = remoteModality.GetRetrieveMethod();
 
     Json::Value body;
     if (call.ParseJsonRequest(body))
     {
       OrthancConfiguration::ReaderLock lock;
 
-      targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, context.GetDefaultLocalApplicationEntityTitle());
+      targetAet = Toolbox::GetJsonStringField(body, KEY_TARGET_AET, targetAet); 
+
       timeout = Toolbox::GetJsonIntegerField(body, KEY_TIMEOUT, -1);
       
       std::string strRetrieveMethod = SerializationToolbox::ReadString(body, KEY_RETRIEVE_METHOD, "");
@@ -950,10 +955,6 @@
       {
         call.BodyToString(targetAet);
       }
-      else
-      {
-        targetAet = context.GetDefaultLocalApplicationEntityTitle();
-      }
     }
     
     if (retrieveMethod == RetrieveMethod_SystemDefault)
@@ -1484,8 +1485,7 @@
         .SetRequestField(KEY_RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings,
                          "List of the Orthanc identifiers of all the DICOM resources to be sent", true)
         .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String,
-                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
-                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option or `LocalAet` in the `DicomModalities`. ", false)
         .SetRequestField(KEY_CALLED_AET, RestApiCallDocumentation::Type_String,
                          "Called AET that is used for this commands, defaults to `AET` configuration option. "
                          "Allows you to overwrite the destination AET for a specific operation.", false)
@@ -1517,15 +1517,20 @@
 
     GetInstancesToExport(request, *job, remote, call);
 
-    std::string localAet = Toolbox::GetJsonStringField
-      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
+    RemoteModalityParameters remoteModality = MyGetModalityUsingSymbolicName(remote);
+
+    std::string localAet = context.GetDefaultLocalApplicationEntityTitle(); // from the global configuration file
+    if (remoteModality.HasLocalAet()) // from the DicomModalities configuration
+    {
+      localAet = remoteModality.GetLocalAet();
+    }
+    localAet = Toolbox::GetJsonStringField(request, KEY_LOCAL_AET, localAet);  // from the payload
+    
     std::string moveOriginatorAET = Toolbox::GetJsonStringField
       (request, KEY_MOVE_ORIGINATOR_AET, context.GetDefaultLocalApplicationEntityTitle());
     int moveOriginatorID = Toolbox::GetJsonIntegerField
       (request, KEY_MOVE_ORIGINATOR_ID, 0 /* By default, not a C-MOVE */);
 
-    RemoteModalityParameters remoteModality = MyGetModalityUsingSymbolicName(remote);
-
     remoteModality.SetApplicationEntityTitle(Toolbox::GetJsonStringField
       (request, KEY_CALLED_AET, remoteModality.GetApplicationEntityTitle()));
     remoteModality.SetHost(Toolbox::GetJsonStringField
@@ -1609,16 +1614,21 @@
     }
 
     ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
-    
-    std::string localAet = Toolbox::GetJsonStringField
-      (request, KEY_LOCAL_AET, context.GetDefaultLocalApplicationEntityTitle());
-
-    const RemoteModalityParameters source =
+
+    const RemoteModalityParameters sourceModality =
       MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
+    std::string localAet = context.GetDefaultLocalApplicationEntityTitle(); // from the global configuration file
+    if (sourceModality.HasLocalAet()) // from the DicomModalities configuration
+    {
+      localAet = sourceModality.GetLocalAet();
+    }
+    localAet = Toolbox::GetJsonStringField(request, KEY_LOCAL_AET, localAet);  // from the payload
+
+
     job.SetQueryFormat(DicomToJsonFormat_Short);
     job.SetLocalAet(localAet);
-    job.SetRemoteModality(source);
+    job.SetRemoteModality(sourceModality);
 
     if (request.isMember(KEY_TIMEOUT))
     {
@@ -1656,8 +1666,7 @@
         .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String,
                          "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true)
         .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String,
-                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
-                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option or `LocalAet` in the `DicomModalities`. ", false)
         .SetRequestField(KEY_TARGET_AET, RestApiCallDocumentation::Type_String,
                          "Target AET that will be used by the remote DICOM modality as a target for its C-STORE SCU "
                          "commands, defaults to `DicomAet` configuration option in order to do a simple query/retrieve", false)
@@ -1709,8 +1718,7 @@
         .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String,
                          "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true)
         .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String,
-                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
-                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option or `LocalAet` in the `DicomModalities`. ", false)
         .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number,
                          "Timeout for the C-GET command, in seconds.  Orthanc will close the DICOM association if no DICOM messages are received within this time period.", false)
         .SetUriArgument("id", "Identifier of the modality of interest");
--- a/OrthancServer/Sources/QueryRetrieveHandler.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancServer/Sources/QueryRetrieveHandler.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -108,6 +108,11 @@
     {
       OrthancConfiguration::ReaderLock lock;
       lock.GetConfiguration().GetDicomModalityUsingSymbolicName(modality_, symbolicName);
+
+      if (modality_.HasLocalAet())
+      {
+        SetLocalAet(modality_.GetLocalAet());
+      }
     }
   }
 
--- a/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomGetScuJob.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -30,11 +30,6 @@
 #include <dcmtk/dcmnet/dimse.h>
 #include <algorithm>
 
-static const char* const LOCAL_AET = "LocalAet";
-static const char* const QUERY = "Query";
-static const char* const QUERY_FORMAT = "QueryFormat";  // New in 1.9.5
-static const char* const REMOTE = "Remote";
-static const char* const TIMEOUT = "Timeout";
 
 namespace Orthanc
 {
--- a/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Wed Apr 22 13:09:28 2026 +0200
+++ b/OrthancServer/Sources/ServerJobs/DicomMoveScuJob.cpp	Mon Apr 27 16:02:50 2026 +0200
@@ -27,12 +27,7 @@
 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 #include "../ServerContext.h"
 
-static const char* const LOCAL_AET = "LocalAet";
-static const char* const QUERY = "Query";
-static const char* const QUERY_FORMAT = "QueryFormat";  // New in 1.9.5
-static const char* const REMOTE = "Remote";
 static const char* const TARGET_AET = "TargetAet";
-static const char* const TIMEOUT = "Timeout";
 
 namespace Orthanc
 {