changeset 4463:522e13a60cfc

"LocalAet" in "DicomModalities" to overwrite global "DicomAet" for SCU on a per-modality basis
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 Jan 2021 17:36:23 +0100
parents da460bef88f8
children e8c7be7a02a9
files NEWS OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h OrthancFramework/UnitTestsSources/JobsTests.cpp OrthancServer/Resources/Configuration.json OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp
diffstat 7 files changed, 97 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Jan 20 17:02:10 2021 +0100
+++ b/NEWS	Wed Jan 20 17:36:23 2021 +0100
@@ -12,6 +12,7 @@
   - "DicomTlsTrustedCertificates" to provide the list of TLS certificates to be trusted by Orthanc
   - "UseDicomTls" in "DicomModalities" to enable DICOM TLS in outgoing SCU on a per-modality basis
   - "MaximumPduLength" to tune the maximum PDU length (Protocol Data Unit)
+  - "LocalAet" in "DicomModalities" to overwrite global "DicomAet" for SCU on a per-modality basis
 * New command-line option: "--openapi" to write the OpenAPI documentation of the REST API to a file
 * New metadata automatically computed at the series level: "RemoteAET"
 
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp	Wed Jan 20 17:36:23 2021 +0100
@@ -265,9 +265,15 @@
                              "No presentation context was proposed");
     }
 
+    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")
-                      << " from AET \"" << parameters.GetLocalApplicationEntityTitle() 
+                      << " from AET \"" << localAet
                       << "\" to AET \"" << parameters.GetRemoteModality().GetApplicationEntityTitle()
                       << "\" on host " << parameters.GetRemoteModality().GetHost()
                       << ":" << parameters.GetRemoteModality().GetPortNumber() 
@@ -298,7 +304,7 @@
 
     // Set this application's title and the called application's title in the params
     CheckConnecting(parameters, ASC_setAPTitles(
-                      params_, parameters.GetLocalApplicationEntityTitle().c_str(),
+                      params_, localAet.c_str(),
                       parameters.GetRemoteModality().GetApplicationEntityTitle().c_str(), NULL));
 
     // Set the network addresses of the local and remote entities
--- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp	Wed Jan 20 17:36:23 2021 +0100
@@ -45,6 +45,7 @@
 static const char* KEY_MANUFACTURER = "Manufacturer";
 static const char* KEY_PORT = "Port";
 static const char* KEY_USE_DICOM_TLS = "UseDicomTls";
+static const char* KEY_LOCAL_AET = "LocalAet";
 
 
 namespace Orthanc
@@ -64,6 +65,7 @@
     allowNEventReport_ = true;  // For storage commitment
     allowTranscoding_ = true;
     useDicomTls_ = false;
+    localAet_.clear();
   }
 
 
@@ -286,6 +288,11 @@
     {
       useDicomTls_ = SerializationToolbox::ReadBoolean(serialized, KEY_USE_DICOM_TLS);
     }
+
+    if (serialized.isMember(KEY_LOCAL_AET))
+    {
+      localAet_ = SerializationToolbox::ReadString(serialized, KEY_LOCAL_AET);
+    }
   }
 
 
@@ -369,7 +376,8 @@
             !allowNAction_ ||
             !allowNEventReport_ ||
             !allowTranscoding_ ||
-            useDicomTls_);
+            useDicomTls_ ||
+            HasLocalAet());
   }
 
   
@@ -393,6 +401,7 @@
       target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_;
       target[KEY_ALLOW_TRANSCODING] = allowTranscoding_;
       target[KEY_USE_DICOM_TLS] = useDicomTls_;
+      target[KEY_LOCAL_AET] = localAet_;
     }
     else
     {
@@ -443,4 +452,33 @@
   {
     useDicomTls_ = enabled;
   }
+
+  bool RemoteModalityParameters::HasLocalAet() const
+  {
+    return !localAet_.empty();
+  }
+
+  const std::string& RemoteModalityParameters::GetLocalAet() const
+  {
+    if (localAet_.empty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls, "You should have called HasLocalAet()");
+    }
+    else
+    {
+      return localAet_;
+    }
+  }
+
+  void RemoteModalityParameters::SetLocalAet(const std::string& aet)
+  {
+    if (aet.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      localAet_ = aet;
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Wed Jan 20 17:36:23 2021 +0100
@@ -46,6 +46,7 @@
     bool                  allowNEventReport_;
     bool                  allowTranscoding_;
     bool                  useDicomTls_;
+    std::string           localAet_;
     
     void Clear();
 
@@ -100,5 +101,11 @@
     bool IsDicomTlsEnabled() const;
 
     void SetDicomTlsEnabled(bool enabled);
+
+    bool HasLocalAet() const;
+
+    const std::string& GetLocalAet() const;
+
+    void SetLocalAet(const std::string& aet);
   };
 }
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Wed Jan 20 17:36:23 2021 +0100
@@ -1275,6 +1275,7 @@
 
   {
     RemoteModalityParameters modality(s);
+    ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
     ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle());
     ASSERT_EQ("127.0.0.1", modality.GetHost());
     ASSERT_EQ(104u, modality.GetPortNumber());
@@ -1288,6 +1289,8 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
     ASSERT_TRUE(modality.IsTranscodingAllowed());
     ASSERT_FALSE(modality.IsDicomTlsEnabled());
+    ASSERT_FALSE(modality.HasLocalAet());
+    ASSERT_THROW(modality.GetLocalAet(), OrthancException);
   }
 
   s = Json::nullValue;
@@ -1303,6 +1306,7 @@
     ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
     modality.Serialize(s, true);
     ASSERT_EQ(Json::objectValue, s.type());
+    ASSERT_FALSE(modality.HasLocalAet());
   }
 
   {
@@ -1320,6 +1324,7 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
     ASSERT_TRUE(modality.IsTranscodingAllowed());
     ASSERT_FALSE(modality.IsDicomTlsEnabled());
+    ASSERT_FALSE(modality.HasLocalAet());
   }
 
   s["Port"] = "46";
@@ -1372,6 +1377,23 @@
     }
   }
 
+  s = Json::nullValue;
+
+  {
+    RemoteModalityParameters modality;
+    modality.SetLocalAet("hello");
+    ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
+    modality.Serialize(s, true);
+    ASSERT_EQ(Json::objectValue, s.type());
+    ASSERT_TRUE(modality.HasLocalAet());
+  }
+
+  {
+    RemoteModalityParameters modality(s);
+    ASSERT_TRUE(modality.HasLocalAet());
+    ASSERT_EQ("hello", modality.GetLocalAet());
+  }
+
   {
     Json::Value t;
     t["AllowStorageCommitment"] = false;
@@ -1388,6 +1410,8 @@
     ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
     ASSERT_TRUE(modality.IsTranscodingAllowed());
     ASSERT_FALSE(modality.IsDicomTlsEnabled());
+    ASSERT_FALSE(modality.HasLocalAet());
+    ASSERT_THROW(modality.GetLocalAet(), OrthancException);
   }
 
   {
@@ -1399,6 +1423,7 @@
     t["Port"] = "104";
     t["AllowTranscoding"] = false;
     t["UseDicomTls"] = true;
+    t["LocalAet"] = "world";
     
     RemoteModalityParameters modality(t);
     ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
@@ -1409,6 +1434,8 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
     ASSERT_FALSE(modality.IsTranscodingAllowed());
     ASSERT_TRUE(modality.IsDicomTlsEnabled());
+    ASSERT_TRUE(modality.HasLocalAet());
+    ASSERT_EQ("world", modality.GetLocalAet());
   }
 
   {
@@ -1428,6 +1455,8 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
     ASSERT_TRUE(modality.IsTranscodingAllowed());
     ASSERT_FALSE(modality.IsDicomTlsEnabled());
+    ASSERT_FALSE(modality.HasLocalAet());
+    ASSERT_THROW(modality.GetLocalAet(), OrthancException);
   }
 }
 
@@ -1456,6 +1485,8 @@
     ASSERT_EQ("ORTHANC", b.GetLocalApplicationEntityTitle());
     ASSERT_EQ(DicomAssociationParameters::GetDefaultMaximumPduLength(), b.GetMaximumPduLength());
     ASSERT_FALSE(b.GetRemoteModality().IsDicomTlsEnabled());
+    ASSERT_FALSE(b.GetRemoteModality().HasLocalAet());
+    ASSERT_THROW(b.GetRemoteModality().GetLocalAet(), OrthancException);
   }
 
   {
--- a/OrthancServer/Resources/Configuration.json	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancServer/Resources/Configuration.json	Wed Jan 20 17:36:23 2021 +0100
@@ -330,6 +330,10 @@
      * The "UseDicomTls" option specifies whether DICOM TLS should be
      * used when opening a SCU connection from Orthanc to this remote
      * modality. By default, DICOM TLS is not enabled.
+     *
+     * The "LocalAet" allows to overwrite the global "DicomAet"
+     * configuration option in order to specify another AET for
+     * Orthanc when initiating an SCU to this very specific modality.
      **/
     //"untrusted" : {
     //  "AET" : "ORTHANC",
@@ -344,6 +348,7 @@
     //  "AllowStorageCommitment" : false,  // new in 1.6.0
     //  "AllowTranscoding" : true,         // new in 1.7.0
     //  "UseDicomTls" : false              // new in 1.9.0
+    //  "LocalAet" : "HELLO"               // new in 1.9.0
     //}
   },
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Wed Jan 20 17:02:10 2021 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp	Wed Jan 20 17:36:23 2021 +0100
@@ -671,7 +671,8 @@
                          "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", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
+                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
         .SetAnswerField("ID", RestApiCallDocumentation::Type_JsonObject,
                         "Identifier of the query, to be used with `/queries/{id}`")
         .SetAnswerField("Path", RestApiCallDocumentation::Type_JsonObject,
@@ -1397,7 +1398,8 @@
         .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", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
+                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false)
         .SetRequestField(KEY_MOVE_ORIGINATOR_AET, RestApiCallDocumentation::Type_String,
                          "Move originator AET that is used for this commands, in order to fake a C-MOVE SCU", false)
         .SetRequestField(KEY_MOVE_ORIGINATOR_ID, RestApiCallDocumentation::Type_Number,
@@ -1506,7 +1508,8 @@
         .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", false)
+                         "Local AET that is used for this commands, defaults to `DicomAet` configuration option. "
+                         "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", 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)