changeset 775:d3ba35466225 lua-scripting

integration mainline -> lua-scripting
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 30 Apr 2014 15:35:10 +0200
parents a64ca424e0e2 (current diff) 4e3593c3511d (diff)
children 9ae0bb3f188b
files UnitTestsSources/MultiThreading.cpp
diffstat 23 files changed, 1027 insertions(+), 214 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Apr 24 12:01:05 2014 +0200
+++ b/CMakeLists.txt	Wed Apr 30 15:35:10 2014 +0200
@@ -203,6 +203,7 @@
   OrthancServer/DicomProtocol/DicomFindAnswers.cpp
   OrthancServer/DicomProtocol/DicomServer.cpp
   OrthancServer/DicomProtocol/DicomUserConnection.cpp
+  OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp
   OrthancServer/FromDcmtkBridge.cpp
   OrthancServer/Internals/CommandDispatcher.cpp
   OrthancServer/Internals/FindScp.cpp
--- a/Core/MultiThreading/ILockable.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/Core/MultiThreading/ILockable.h	Wed Apr 30 15:35:10 2014 +0200
@@ -38,13 +38,16 @@
 {
   class ILockable : public boost::noncopyable
   {
+    friend class Locker;
+
+  protected:
+    virtual void Lock() = 0;
+
+    virtual void Unlock() = 0;
+
   public:
     virtual ~ILockable()
     {
     }
-
-    virtual void Lock() = 0;
-
-    virtual void Unlock() = 0;
   };
 }
--- a/Core/MultiThreading/Locker.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/Core/MultiThreading/Locker.h	Wed Apr 30 15:35:10 2014 +0200
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class Locker
+  class Locker : public boost::noncopyable
   {
   private:
     ILockable& lockable_;
--- a/Core/MultiThreading/Mutex.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/Core/MultiThreading/Mutex.h	Wed Apr 30 15:35:10 2014 +0200
@@ -43,13 +43,14 @@
 
     PImpl *pimpl_;
 
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
   public:
     Mutex();
 
     ~Mutex();
-
-    virtual void Lock();
-
-    virtual void Unlock();
   };
 }
--- a/Core/MultiThreading/ReaderWriterLock.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/Core/MultiThreading/ReaderWriterLock.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -46,11 +46,7 @@
     private:
       boost::shared_mutex& lock_;
 
-    public:
-      ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-
+    protected:
       virtual void Lock()
       {
         lock_.lock_shared();
@@ -60,6 +56,11 @@
       {
         lock_.unlock_shared();        
       }
+
+    public:
+      ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
     };
 
 
@@ -68,11 +69,7 @@
     private:
       boost::shared_mutex& lock_;
 
-    public:
-      WriterLockable(boost::shared_mutex& lock) : lock_(lock)
-      {
-      }
-
+    protected:
       virtual void Lock()
       {
         lock_.lock();
@@ -82,6 +79,12 @@
       {
         lock_.unlock();        
       }
+
+    public:
+      WriterLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
+
     };
   }
 
--- a/NEWS	Thu Apr 24 12:01:05 2014 +0200
+++ b/NEWS	Wed Apr 30 15:35:10 2014 +0200
@@ -1,6 +1,9 @@
 Pending changes in the mainline
 ===============================
 
+* Reuse of the previous SCU connection to avoid unecessary handshakes
+* Dynamic negotiation of SOP classes for C-Store SCU
+* Fix missing licensing terms about reuse of some DCMTK code
 
 
 Version 0.7.4 (2014/04/16)
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -30,6 +30,54 @@
  **/
 
 
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
 #include "DicomUserConnection.h"
 
 #include "../../Core/OrthancException.h"
@@ -60,6 +108,15 @@
 
 static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax;
 
+/**
+ * "If we have more than 64 storage SOP classes, tools such as
+ * storescu will fail because they attempt to negotiate two
+ * presentation contexts for each SOP class, and there is a total
+ * limit of 128 contexts for one association."
+ **/
+static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64;
+
+
 namespace Orthanc
 {
   struct DicomUserConnection::PImpl
@@ -103,55 +160,38 @@
   }
 
 
-  void DicomUserConnection::CopyParameters(const DicomUserConnection& other)
+  static void RegisterStorageSOPClass(T_ASC_Parameters* params,
+                                      unsigned int& presentationContextId,
+                                      const std::string& sopClass,
+                                      const char* asPreferred[],
+                                      std::vector<const char*>& asFallback)
   {
-    Close();
-    localAet_ = other.localAet_;
-    distantAet_ = other.distantAet_;
-    distantHost_ = other.distantHost_;
-    distantPort_ = other.distantPort_;
-    manufacturer_ = other.manufacturer_;
-    preferredTransferSyntax_ = other.preferredTransferSyntax_;
+    Check(ASC_addPresentationContext(params, presentationContextId, 
+                                     sopClass.c_str(), asPreferred, 1));
+    presentationContextId += 2;
+
+    if (asFallback.size() > 0)
+    {
+      Check(ASC_addPresentationContext(params, presentationContextId, 
+                                       sopClass.c_str(), &asFallback[0], asFallback.size()));
+      presentationContextId += 2;
+    }
   }
-
-
+  
+    
   void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
   {
-    // Fallback transfer syntaxes
+    // Flatten an array with the preferred transfer syntax
+    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
+
+    // Setup the fallback transfer syntaxes
     std::set<std::string> fallbackSyntaxes;
     fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax);
     fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax);
     fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax);
-
-    // Transfer syntaxes for C-ECHO, C-FIND and C-MOVE
-    std::vector<std::string> transferSyntaxes;
-    transferSyntaxes.push_back(UID_VerificationSOPClass);
-    transferSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-    transferSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-    transferSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-
-    // TODO: Allow the set below to be configured
-    std::set<std::string> uselessSyntaxes;
-    uselessSyntaxes.insert(UID_BlendingSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_ColorSoftcopyPresentationStateStorage);
-    uselessSyntaxes.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
-
-    // Add the transfer syntaxes for C-STORE
-    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
-    {
-      // Test to make some room to allow the ECHO and FIND requests
-      if (uselessSyntaxes.find(dcmShortSCUStorageSOPClassUIDs[i]) == uselessSyntaxes.end())
-      {
-        transferSyntaxes.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
-      }
-    }
-
-    // Flatten the fallback transfer syntaxes array
-    const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
-
     fallbackSyntaxes.erase(preferredTransferSyntax);
 
+    // Flatten an array with the fallback transfer syntaxes
     std::vector<const char*> asFallback;
     asFallback.reserve(fallbackSyntaxes.size());
     for (std::set<std::string>::const_iterator 
@@ -160,19 +200,28 @@
       asFallback.push_back(it->c_str());
     }
 
+    CheckStorageSOPClassesInvariant();
     unsigned int presentationContextId = 1;
-    for (size_t i = 0; i < transferSyntaxes.size(); i++)
+
+    for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
+         it != reservedStorageSOPClasses_.end(); it++)
     {
-      Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
-                                       transferSyntaxes[i].c_str(), asPreferred, 1));
-      presentationContextId += 2;
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
 
-      if (asFallback.size() > 0)
-      {
-        Check(ASC_addPresentationContext(pimpl_->params_, presentationContextId, 
-                                         transferSyntaxes[i].c_str(), &asFallback[0], asFallback.size()));
-        presentationContextId += 2;
-      }
+    for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
+         it != storageSOPClasses_.end(); it++)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
+    }
+
+    for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
+         it != defaultStorageSOPClasses_.end(); it++)
+    {
+      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                              *it, asPreferred, asFallback);
     }
   }
 
@@ -192,8 +241,16 @@
     DcmFileFormat dcmff;
     Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength));
 
+    // Determine the storage SOP class UID for this instance
+    static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
+    OFString sopClassUid;
+    if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good())
+    {
+      connection.AddStorageSOPClass(sopClassUid.c_str());
+    }
+
     // Determine whether a new presentation context must be
-    // negociated, depending on the transfer syntax of this instance
+    // negotiated, depending on the transfer syntax of this instance
     DcmXfer xfer(dcmff.getDataset()->getOriginalXfer());
     const std::string syntax(xfer.getXferID());
     bool isGeneric = IsGenericTransferSyntax(syntax);
@@ -201,8 +258,8 @@
     if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()))
     {
       // Making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax. Renegociate the connection.
-      LOG(INFO) << "Renegociating a C-Store association due to a change in the transfer syntax";
+      // the transfer syntax. Renegotiate the connection.
+      LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
 
       if (isGeneric)
       {
@@ -212,7 +269,11 @@
       {
         connection.SetPreferredTransferSyntax(syntax);
       }
+    }
 
+    if (!connection.IsOpen())
+    {
+      LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters";
       connection.Open();
     }
 
@@ -232,7 +293,7 @@
       if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
       if (!modalityName) modalityName = "unknown SOP class";
       throw OrthancException("DicomUserConnection: No presentation context for modality " + 
-                              std::string(modalityName));
+                             std::string(modalityName));
     }
 
     // Prepare the transmission of data
@@ -288,88 +349,88 @@
     std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(fields));
     switch (model)
     {
-    case FindRootModel_Patient:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
-      sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
+      case FindRootModel_Patient:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
+        sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
       
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Patient ID
-      if (!fields.HasTag(0x0010, 0x0020))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), "");
+        // Patient ID
+        if (!fields.HasTag(0x0010, 0x0020))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0010, 0x0020), "");
 
-      break;
+        break;
 
-    case FindRootModel_Study:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+      case FindRootModel_Study:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      break;
+        break;
 
-    case FindRootModel_Series:
-      DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+      case FindRootModel_Series:
+        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      // Series instance UID
-      if (!fields.HasTag(0x0020, 0x000e))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
+        // Series instance UID
+        if (!fields.HasTag(0x0020, 0x000e))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
 
-      break;
+        break;
 
-    case FindRootModel_Instance:
-      if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-          manufacturer_ == ModalityManufacturer_Dcm4Chee)
-      {
-        // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-        // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-        // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
-      }
-      else
-      {
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
-      }
+      case FindRootModel_Instance:
+        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
+            manufacturer_ == ModalityManufacturer_Dcm4Chee)
+        {
+          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
+          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
+          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "IMAGE");
+        }
+        else
+        {
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "INSTANCE");
+        }
 
-      sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
+        sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
 
-      // Accession number
-      if (!fields.HasTag(0x0008, 0x0050))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
+        // Accession number
+        if (!fields.HasTag(0x0008, 0x0050))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0050), "");
 
-      // Study instance UID
-      if (!fields.HasTag(0x0020, 0x000d))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
+        // Study instance UID
+        if (!fields.HasTag(0x0020, 0x000d))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000d), "");
 
-      // Series instance UID
-      if (!fields.HasTag(0x0020, 0x000e))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
+        // Series instance UID
+        if (!fields.HasTag(0x0020, 0x000e))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0020, 0x000e), "");
 
-      // SOP Instance UID
-      if (!fields.HasTag(0x0008, 0x0018))
-        DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), "");
+        // SOP Instance UID
+        if (!fields.HasTag(0x0008, 0x0018))
+          DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0018), "");
 
-      break;
+        break;
 
-    default:
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
     // Figure out which of the accepted presentation contexts should be used
@@ -501,6 +562,35 @@
   }
 
 
+  void DicomUserConnection::ResetStorageSOPClasses()
+  {
+    CheckStorageSOPClassesInvariant();
+
+    storageSOPClasses_.clear();
+    defaultStorageSOPClasses_.clear();
+
+    // Copy the short list of storage SOP classes from DCMTK, making
+    // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE.
+
+    std::set<std::string> uncommon;
+    uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
+
+    // Add the storage syntaxes for C-STORE
+    for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
+    {
+      if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end())
+      {
+        defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]);
+      }
+    }
+
+    CheckStorageSOPClassesInvariant();
+  }
+
+
   DicomUserConnection::DicomUserConnection() : 
     pimpl_(new PImpl),
     preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX),
@@ -514,6 +604,14 @@
     pimpl_->net_ = NULL;
     pimpl_->params_ = NULL;
     pimpl_->assoc_ = NULL;
+
+    // SOP classes for C-ECHO, C-FIND and C-MOVE
+    reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
+    reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+
+    ResetStorageSOPClasses();
   }
 
   DicomUserConnection::~DicomUserConnection()
@@ -521,6 +619,16 @@
     Close();
   }
 
+
+  void DicomUserConnection::Connect(const RemoteModalityParameters& parameters)
+  {
+    SetDistantApplicationEntityTitle(parameters.GetApplicationEntityTitle());
+    SetDistantHost(parameters.GetHost());
+    SetDistantPort(parameters.GetPort());
+    SetDistantManufacturer(parameters.GetManufacturer());
+  }
+
+
   void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
   {
     if (localAet_ != aet)
@@ -594,6 +702,11 @@
       return;
     }
 
+    LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() 
+              << "\" to AET \"" << GetDistantApplicationEntityTitle() << "\" on host "
+              << GetDistantHost() << ":" << GetDistantPort() 
+              << " (manufacturer: " << EnumerationToString(GetDistantManufacturer()) << ")";
+
     Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ 30, &pimpl_->net_));
     Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
 
@@ -607,11 +720,11 @@
     char distantHostAndPort[HOST_NAME_MAX];
 
 #ifdef _MSC_VER
-	_snprintf
+    _snprintf
 #else
-	snprintf
+      snprintf
 #endif
-		(distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_);
+      (distantHostAndPort, HOST_NAME_MAX - 1, "%s:%d", distantHost_.c_str(), distantPort_);
 
     Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, distantHostAndPort));
 
@@ -742,4 +855,60 @@
     dcmConnectionTimeout.set(seconds);
   }
 
+
+  void DicomUserConnection::CheckStorageSOPClassesInvariant() const
+  {
+    assert(storageSOPClasses_.size() + 
+           defaultStorageSOPClasses_.size() + 
+           reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES);
+  }
+
+  void DicomUserConnection::AddStorageSOPClass(const char* sop)
+  {
+    CheckStorageSOPClassesInvariant();
+
+    if (storageSOPClasses_.find(sop) != storageSOPClasses_.end())
+    {
+      // This storage SOP class is already explicitly registered. Do
+      // nothing.
+      return;
+    }
+
+    if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end())
+    {
+      // This storage SOP class is not explicitly registered, but is
+      // used by default. Just register it explicitly.
+      defaultStorageSOPClasses_.erase(sop);
+      storageSOPClasses_.insert(sop);
+
+      CheckStorageSOPClassesInvariant();
+      return;
+    }
+
+    // This storage SOP class is neither explicitly, nor implicitly
+    // registered. Close the connection and register it explicitly.
+
+    Close();
+
+    if (reservedStorageSOPClasses_.size() + 
+        storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)  // (*)
+    {
+      // The maximum number of SOP classes is reached
+      ResetStorageSOPClasses();
+      defaultStorageSOPClasses_.erase(sop);
+    }
+    else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + 
+             defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES)
+    {
+      // Make room in the default storage syntaxes
+      assert(defaultStorageSOPClasses_.size() > 0);  // Necessarily true because condition (*) is false
+      defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin());
+    }
+
+    // Explicitly register the new storage syntax
+    storageSOPClasses_.insert(sop);
+
+    CheckStorageSOPClassesInvariant();
+  }
+
 }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Wed Apr 30 15:35:10 2014 +0200
@@ -34,10 +34,12 @@
 
 #include "DicomFindAnswers.h"
 #include "../ServerEnumerations.h"
+#include "RemoteModalityParameters.h"
 
 #include <stdint.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
+#include <list>
 
 namespace Orthanc
 {
@@ -62,6 +64,9 @@
     std::string distantHost_;
     uint16_t distantPort_;
     ModalityManufacturer manufacturer_;
+    std::set<std::string> storageSOPClasses_;
+    std::list<std::string> reservedStorageSOPClasses_;
+    std::set<std::string> defaultStorageSOPClasses_;
 
     void CheckIsOpen() const;
 
@@ -74,12 +79,16 @@
     void Move(const std::string& targetAet,
               const DicomMap& fields);
 
+    void ResetStorageSOPClasses();
+
+    void CheckStorageSOPClassesInvariant() const;
+
   public:
     DicomUserConnection();
 
     ~DicomUserConnection();
 
-    void CopyParameters(const DicomUserConnection& other);
+    void Connect(const RemoteModalityParameters& parameters);
 
     void SetLocalApplicationEntityTitle(const std::string& aet);
 
@@ -125,6 +134,8 @@
       return preferredTransferSyntax_;
     }
 
+    void AddStorageSOPClass(const char* sop);
+
     void Open();
 
     void Close();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Wed Apr 30 15:35:10 2014 +0200
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "../ServerEnumerations.h"
+
+#include <string>
+
+namespace Orthanc
+{
+  class RemoteModalityParameters
+  {
+    // TODO Use the flyweight pattern for this class
+
+  private:
+    std::string  symbolicName_;
+    std::string  aet_;
+    std::string  host_;
+    int  port_;
+    ModalityManufacturer  manufacturer_;
+
+  public:
+    RemoteModalityParameters() :
+      symbolicName_(""),
+      aet_(""),
+      host_(""),
+      port_(104),
+      manufacturer_(ModalityManufacturer_Generic)
+    {
+    }
+
+    RemoteModalityParameters(const std::string& symbolic,
+                             const std::string& aet,
+                             const std::string& host,
+                             int port,
+                             ModalityManufacturer manufacturer) :
+      symbolicName_(symbolic),
+      aet_(aet),
+      host_(host),
+      port_(port),
+      manufacturer_(manufacturer)
+    {
+    }
+
+    RemoteModalityParameters(const std::string& aet,
+                             const std::string& host,
+                             int port,
+                             ModalityManufacturer manufacturer) :
+      symbolicName_(""),
+      aet_(aet),
+      host_(host),
+      port_(port),
+      manufacturer_(manufacturer)
+    {
+    }
+
+    const std::string& GetSymbolicName() const
+    {
+      return symbolicName_;
+    }
+
+    const std::string& GetApplicationEntityTitle() const
+    {
+      return aet_;
+    }
+
+    const std::string& GetHost() const
+    {
+      return host_;
+    }
+
+    int GetPort() const
+    {
+      return port_;
+    }
+
+    ModalityManufacturer GetManufacturer() const
+    {
+      return manufacturer_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -0,0 +1,178 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 "ReusableDicomUserConnection.h"
+
+#include "../../Core/OrthancException.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  static boost::posix_time::ptime Now()
+  {
+    return boost::posix_time::microsec_clock::local_time();
+  }
+
+  void ReusableDicomUserConnection::Open(const std::string& remoteAet,
+                                         const std::string& address,
+                                         int port,
+                                         ModalityManufacturer manufacturer)
+  {
+    if (connection_ != NULL &&
+        connection_->GetDistantApplicationEntityTitle() == remoteAet &&
+        connection_->GetDistantHost() == address &&
+        connection_->GetDistantPort() == port &&
+        connection_->GetDistantManufacturer() == manufacturer)
+    {
+      // The current connection can be reused
+      LOG(INFO) << "Reusing the previous SCU connection";
+      return;
+    }
+
+    Close();
+
+    connection_ = new DicomUserConnection();
+    connection_->SetLocalApplicationEntityTitle(localAet_);
+    connection_->SetDistantApplicationEntityTitle(remoteAet);
+    connection_->SetDistantHost(address);
+    connection_->SetDistantPort(port);
+    connection_->SetDistantManufacturer(manufacturer);
+    connection_->Open();
+  }
+    
+  void ReusableDicomUserConnection::Close()
+  {
+    if (connection_ != NULL)
+    {
+      delete connection_;
+      connection_ = NULL;
+    }
+  }
+
+  void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that)
+  {
+    for (;;)
+    {
+      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+      if (!that->continue_)
+      {
+        //LOG(INFO) << "Finishing the thread watching the global SCU connection";
+        return;
+      }
+
+      {
+        boost::mutex::scoped_lock lock(that->mutex_);
+        if (that->connection_ != NULL &&
+            Now() >= that->lastUse_ + that->timeBeforeClose_)
+        {
+          LOG(INFO) << "Closing the global SCU connection after timeout";
+          that->Close();
+        }
+      }
+    }
+  }
+    
+  ReusableDicomUserConnection::Connection::Connection(ReusableDicomUserConnection& that,
+                                                      const std::string& aet,
+                                                      const std::string& address,
+                                                      int port,
+                                                      ModalityManufacturer manufacturer) :
+    Locker(that)
+  {
+    that.Open(aet, address, port, manufacturer);
+    connection_ = that.connection_;
+  }
+
+
+  ReusableDicomUserConnection::Connection::Connection(ReusableDicomUserConnection& that,
+                                                      const RemoteModalityParameters& remote) :
+    Locker(that)
+  {
+    that.Open(remote.GetApplicationEntityTitle(), remote.GetHost(), 
+              remote.GetPort(), remote.GetManufacturer());
+    connection_ = that.connection_;    
+  }
+
+
+  DicomUserConnection& ReusableDicomUserConnection::Connection::GetConnection()
+  {
+    if (connection_ == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return *connection_;
+  }      
+
+  ReusableDicomUserConnection::ReusableDicomUserConnection() : 
+    connection_(NULL), 
+    timeBeforeClose_(boost::posix_time::seconds(5)),  // By default, close connection after 5 seconds
+    localAet_("ORTHANC")
+  {
+    lastUse_ = Now();
+    continue_ = true;
+    closeThread_ = boost::thread(CloseThread, this);
+  }
+
+  ReusableDicomUserConnection::~ReusableDicomUserConnection()
+  {
+    continue_ = false;
+    closeThread_.join();
+    Close();
+  }
+
+  void ReusableDicomUserConnection::SetMillisecondsBeforeClose(unsigned int ms)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    timeBeforeClose_ = boost::posix_time::milliseconds(ms);
+  }
+
+  void ReusableDicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    Close();
+    localAet_ = aet;
+  }
+
+  void ReusableDicomUserConnection::Lock()
+  {
+    mutex_.lock();
+  }
+
+  void ReusableDicomUserConnection::Unlock()
+  {
+    lastUse_ = Now();
+    mutex_.unlock();
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h	Wed Apr 30 15:35:10 2014 +0200
@@ -0,0 +1,103 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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/>.
+ **/
+
+
+#pragma once
+
+#include "DicomUserConnection.h"
+#include "../../Core/MultiThreading/Locker.h"
+
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace Orthanc
+{
+  class ReusableDicomUserConnection : public ILockable
+  {
+  private:
+    boost::mutex mutex_;
+    DicomUserConnection* connection_;
+    bool continue_;
+    boost::posix_time::time_duration timeBeforeClose_;
+    boost::posix_time::ptime lastUse_;
+    boost::thread closeThread_;
+    std::string localAet_;
+
+    void Open(const std::string& remoteAet,
+              const std::string& address,
+              int port,
+              ModalityManufacturer manufacturer);
+    
+    void Close();
+
+    static void CloseThread(ReusableDicomUserConnection* that);
+
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
+  public:
+    class Connection : public Locker
+    {
+    private:
+      DicomUserConnection* connection_;
+
+    public:
+      Connection(ReusableDicomUserConnection& that,
+                 const RemoteModalityParameters& remote);
+
+      Connection(ReusableDicomUserConnection& that,
+                 const std::string& aet,
+                 const std::string& address,
+                 int port,
+                 ModalityManufacturer manufacturer);
+
+      DicomUserConnection& GetConnection();
+    };
+
+    ReusableDicomUserConnection();
+
+    virtual ~ReusableDicomUserConnection();
+
+    unsigned int GetMillisecondsBeforeClose() const
+    {
+      return timeBeforeClose_.total_milliseconds();
+    }
+
+    void SetMillisecondsBeforeClose(unsigned int ms);
+
+    const std::string& GetLocalApplicationEntityTitle() const;
+
+    void SetLocalApplicationEntityTitle(const std::string& aet);
+  };
+}
+
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -30,6 +30,55 @@
  **/
 
 
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
 #include "CommandDispatcher.h"
 
 #include "FindScp.h"
--- a/OrthancServer/Internals/FindScp.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -30,6 +30,55 @@
  **/
 
 
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
+
 #include "FindScp.h"
 
 #include "../FromDcmtkBridge.h"
--- a/OrthancServer/Internals/MoveScp.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -30,6 +30,55 @@
  **/
 
 
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
 #include "MoveScp.h"
 
 #include <memory>
--- a/OrthancServer/Internals/StoreScp.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -30,6 +30,55 @@
  **/
 
 
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+Copyright (C) 1994-2011, OFFIS e.V.
+All rights reserved.
+
+This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=========================================================================*/
+
+
 #include "StoreScp.h"
 
 #include "../FromDcmtkBridge.h"
--- a/OrthancServer/OrthancInitialization.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -508,25 +508,6 @@
   }
 
 
-  void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection,
-                                          const std::string& name)
-  {
-    std::string aet, address;
-    int port;
-    ModalityManufacturer manufacturer;
-    GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer);
-
-    LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port;
-
-    connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
-    connection.SetDistantApplicationEntityTitle(aet);
-    connection.SetDistantHost(address);
-    connection.SetDistantPort(port);
-    connection.SetDistantManufacturer(manufacturer);
-    connection.Open();
-  }
-
-
   bool IsSameAETitle(const std::string& aet1,
                      const std::string& aet2)
   {
@@ -587,25 +568,31 @@
   }
 
 
-  void ConnectToModalityUsingAETitle(DicomUserConnection& connection,
-                                     const std::string& aet)
+  RemoteModalityParameters GetModalityUsingSymbolicName(const std::string& name)
   {
-    std::string symbolicName, address;
+    std::string aet, address;
     int port;
     ModalityManufacturer manufacturer;
 
-    if (!LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer))
-    {
-      throw OrthancException("Unknown modality: " + aet);
-    }
+    GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer);
 
-    LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port;
+    return RemoteModalityParameters(name, aet, address, port, manufacturer);
+  }
+
 
-    connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
-    connection.SetDistantApplicationEntityTitle(aet);
-    connection.SetDistantHost(address);
-    connection.SetDistantPort(port);
-    connection.SetDistantManufacturer(manufacturer);
-    connection.Open();
+  RemoteModalityParameters GetModalityUsingAet(const std::string& aet)
+  {
+    std::string name, address;
+    int port;
+    ModalityManufacturer manufacturer;
+
+    if (LookupDicomModalityUsingAETitle(aet, name, address, port, manufacturer))
+    {
+      return RemoteModalityParameters(name, aet, address, port, manufacturer);
+    }
+    else
+    {
+      throw OrthancException("Unknown modality for AET: " + aet);
+    }
   }
 }
--- a/OrthancServer/OrthancInitialization.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/OrthancInitialization.h	Wed Apr 30 15:35:10 2014 +0200
@@ -37,7 +37,7 @@
 #include <json/json.h>
 #include <stdint.h>
 #include "../Core/HttpServer/MongooseServer.h"
-#include "DicomProtocol/DicomUserConnection.h"
+#include "DicomProtocol/RemoteModalityParameters.h"
 #include "ServerEnumerations.h"
 
 namespace Orthanc
@@ -86,14 +86,12 @@
   void GetGlobalListOfStringsParameter(std::list<std::string>& target,
                                        const std::string& key);
 
-  void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection,
-                                          const std::string& name);
-
-  void ConnectToModalityUsingAETitle(DicomUserConnection& connection,
-                                     const std::string& aet);
-
   bool IsKnownAETitle(const std::string& aet);
 
   bool IsSameAETitle(const std::string& aet1,
                      const std::string& aet2);
+
+  RemoteModalityParameters GetModalityUsingSymbolicName(const std::string& name);
+
+  RemoteModalityParameters GetModalityUsingAet(const std::string& aet);
 }
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -33,7 +33,6 @@
 
 #include <glog/logging.h>
 
-#include "DicomProtocol/DicomUserConnection.h"
 #include "OrthancInitialization.h"
 
 namespace Orthanc
@@ -47,17 +46,17 @@
     private:
       ServerContext& context_;
       std::vector<std::string> instances_;
-      DicomUserConnection connection_;
       size_t position_;
+      RemoteModalityParameters remote_;
 
     public:
       OrthancMoveRequestIterator(ServerContext& context,
-                                 const std::string& target,
+                                 const std::string& aet,
                                  const std::string& publicId) :
         context_(context),
         position_(0)
       {
-        LOG(INFO) << "Sending resource " << publicId << " to modality \"" << target << "\"";
+        LOG(INFO) << "Sending resource " << publicId << " to modality \"" << aet << "\"";
 
         std::list<std::string> tmp;
         context_.GetIndex().GetChildInstances(tmp, publicId);
@@ -67,8 +66,8 @@
         {
           instances_.push_back(*it);
         }
-    
-        ConnectToModalityUsingAETitle(connection_, target);
+
+        remote_ = GetModalityUsingAet(aet);
       }
 
       virtual unsigned int GetSubOperationCount() const
@@ -87,7 +86,12 @@
 
         std::string dicom;
         context_.ReadFile(dicom, id, FileContentType_Dicom);
-        connection_.Store(dicom);
+
+        {
+          ReusableDicomUserConnection::Connection connection
+            (context_.GetReusableDicomUserConnection(), remote_);
+          connection.GetConnection().Store(dicom);
+        }
 
         return Status_Success;
       }
@@ -121,10 +125,10 @@
   }
 
 
-  IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& target,
+  IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& aet,
                                                           const DicomMap& input)
   {
-    LOG(WARNING) << "Move-SCU request received for AET \"" << target << "\"";
+    LOG(WARNING) << "Move-SCU request received for AET \"" << aet << "\"";
 
 
     /**
@@ -173,6 +177,6 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    return new OrthancMoveRequestIterator(context_, target, publicId);
+    return new OrthancMoveRequestIterator(context_, aet, publicId);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -32,7 +32,6 @@
 
 #include "OrthancRestApi.h"
 
-#include "../DicomProtocol/DicomUserConnection.h"
 #include "../OrthancInitialization.h"
 #include "../../Core/HttpClient.h"
 
@@ -66,6 +65,8 @@
 
   static void DicomFindPatient(RestApi::PostCall& call)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
     DicomMap m;
     DicomMap::SetupFindPatientTemplate(m);
     if (!MergeQueryAndTemplate(m, call.GetPostBody()))
@@ -73,11 +74,11 @@
       return;
     }
 
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", ""));
+    RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote);
 
     DicomFindAnswers answers;
-    connection.FindPatient(answers, m);
+    connection.GetConnection().FindPatient(answers, m);
 
     Json::Value result;
     answers.ToJson(result);
@@ -86,6 +87,8 @@
 
   static void DicomFindStudy(RestApi::PostCall& call)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
     DicomMap m;
     DicomMap::SetupFindStudyTemplate(m);
     if (!MergeQueryAndTemplate(m, call.GetPostBody()))
@@ -99,11 +102,11 @@
       return;
     }        
       
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", ""));
-  
+    RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote);
+
     DicomFindAnswers answers;
-    connection.FindStudy(answers, m);
+    connection.GetConnection().FindStudy(answers, m);
 
     Json::Value result;
     answers.ToJson(result);
@@ -112,6 +115,8 @@
 
   static void DicomFindSeries(RestApi::PostCall& call)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
     DicomMap m;
     DicomMap::SetupFindSeriesTemplate(m);
     if (!MergeQueryAndTemplate(m, call.GetPostBody()))
@@ -126,11 +131,11 @@
       return;
     }        
          
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", ""));
-  
+    RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote);
+
     DicomFindAnswers answers;
-    connection.FindSeries(answers, m);
+    connection.GetConnection().FindSeries(answers, m);
 
     Json::Value result;
     answers.ToJson(result);
@@ -139,6 +144,8 @@
 
   static void DicomFindInstance(RestApi::PostCall& call)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
     DicomMap m;
     DicomMap::SetupFindInstanceTemplate(m);
     if (!MergeQueryAndTemplate(m, call.GetPostBody()))
@@ -154,11 +161,11 @@
       return;
     }        
          
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", ""));
-  
+    RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote);
+
     DicomFindAnswers answers;
-    connection.FindInstance(answers, m);
+    connection.GetConnection().FindInstance(answers, m);
 
     Json::Value result;
     answers.ToJson(result);
@@ -167,6 +174,8 @@
 
   static void DicomFind(RestApi::PostCall& call)
   {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
     DicomMap m;
     DicomMap::SetupFindPatientTemplate(m);
     if (!MergeQueryAndTemplate(m, call.GetPostBody()))
@@ -174,11 +183,11 @@
       return;
     }
  
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", ""));
-  
+    RemoteModalityParameters remote = GetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), remote);
+
     DicomFindAnswers patients;
-    connection.FindPatient(patients, m);
+    connection.GetConnection().FindPatient(patients, m);
 
     // Loop over the found patients
     Json::Value result = Json::arrayValue;
@@ -195,7 +204,7 @@
       m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
 
       DicomFindAnswers studies;
-      connection.FindStudy(studies, m);
+      connection.GetConnection().FindStudy(studies, m);
 
       patient["Studies"] = Json::arrayValue;
       
@@ -214,7 +223,7 @@
         m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
 
         DicomFindAnswers series;
-        connection.FindSeries(series, m);
+        connection.GetConnection().FindSeries(series, m);
 
         // Loop over the found series
         study["Series"] = Json::arrayValue;
@@ -309,8 +318,8 @@
       return;
     }
 
-    DicomUserConnection connection;
-    ConnectToModalityUsingSymbolicName(connection, remote);
+    RemoteModalityParameters p = GetModalityUsingSymbolicName(remote);
+    ReusableDicomUserConnection::Connection connection(context.GetReusableDicomUserConnection(), p);
 
     for (std::list<std::string>::const_iterator 
            it = instances.begin(); it != instances.end(); ++it)
@@ -319,7 +328,7 @@
 
       std::string dicom;
       context.ReadFile(dicom, *it, FileContentType_Dicom);
-      connection.Store(dicom);
+      connection.GetConnection().Store(dicom);
     }
 
     call.GetOutput().AnswerBuffer("{}", "application/json");
--- a/OrthancServer/ServerContext.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -35,6 +35,7 @@
 #include "../Core/HttpServer/FilesystemHttpSender.h"
 #include "../Core/Lua/LuaFunctionCall.h"
 #include "ServerToolbox.h"
+#include "OrthancInitialization.h"
 
 #include <glog/logging.h>
 #include <EmbeddedResources.h>
@@ -65,6 +66,9 @@
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE)
   {
+    scu_.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
+    //scu_.SetMillisecondsBeforeClose(1);  // The connection is always released
+
     lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
   }
 
--- a/OrthancServer/ServerContext.h	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/ServerContext.h	Wed Apr 30 15:35:10 2014 +0200
@@ -39,6 +39,7 @@
 #include "../Core/Lua/LuaContext.h"
 #include "ServerIndex.h"
 #include "FromDcmtkBridge.h"
+#include "DicomProtocol/ReusableDicomUserConnection.h"
 
 namespace Orthanc
 {
@@ -70,6 +71,7 @@
     
     DicomCacheProvider provider_;
     MemoryCache dicomCache_;
+    ReusableDicomUserConnection scu_;
 
     LuaContext lua_;
 
@@ -150,5 +152,10 @@
     {
       return accessor_.IsStoreMD5();
     }
+
+    ReusableDicomUserConnection& GetReusableDicomUserConnection()
+    {
+      return scu_;
+    }
   };
 }
--- a/OrthancServer/main.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/OrthancServer/main.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -41,7 +41,7 @@
 #include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/DicomFormat/DicomArray.h"
 #include "DicomProtocol/DicomServer.h"
-#include "DicomProtocol/DicomUserConnection.h"
+#include "DicomProtocol/ReusableDicomUserConnection.h"
 #include "OrthancInitialization.h"
 #include "ServerContext.h"
 #include "OrthancFindRequestHandler.h"
--- a/UnitTestsSources/MultiThreading.cpp	Thu Apr 24 12:01:05 2014 +0200
+++ b/UnitTestsSources/MultiThreading.cpp	Wed Apr 30 15:35:10 2014 +0200
@@ -214,7 +214,31 @@
 
 
 
+#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h"
 
+TEST(ReusableDicomUserConnection, DISABLED_Basic)
+{
+  ReusableDicomUserConnection c;
+  c.SetMillisecondsBeforeClose(200);
+  printf("START\n"); fflush(stdout);
+
+  {
+    ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281");
+  }
+
+  printf("**\n"); fflush(stdout);
+  Toolbox::USleep(1000000);
+  printf("**\n"); fflush(stdout);
+
+  {
+    ReusableDicomUserConnection::Connection cc(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    cc.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277");
+  }
+
+  Toolbox::ServerBarrier();
+  printf("DONE\n"); fflush(stdout);
+}