changeset 3681:9dac85e807c2 storage-commitment

integration mainline->storage-commitment
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 Feb 2020 20:36:47 +0100
parents 340bdcc298e9 (current diff) 453c0ece560a (diff)
children 898903022836
files Core/DicomFormat/DicomMap.cpp NEWS OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h
diffstat 10 files changed, 248 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomMap.cpp	Mon Feb 17 17:54:40 2020 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Thu Feb 20 20:36:47 2020 +0100
@@ -1059,8 +1059,8 @@
   }
 
 
-  void DicomMap::ExtractMainDicomTagsInternal(const DicomMap& other,
-                                              ResourceType level)
+  void DicomMap::MergeMainDicomTags(const DicomMap& other,
+                                    ResourceType level)
   {
     const MainDicomTag* tags = NULL;
     size_t size = 0;
@@ -1085,10 +1085,10 @@
   void DicomMap::ExtractMainDicomTags(const DicomMap& other)
   {
     Clear();
-    ExtractMainDicomTagsInternal(other, ResourceType_Patient);
-    ExtractMainDicomTagsInternal(other, ResourceType_Study);
-    ExtractMainDicomTagsInternal(other, ResourceType_Series);
-    ExtractMainDicomTagsInternal(other, ResourceType_Instance);
+    MergeMainDicomTags(other, ResourceType_Patient);
+    MergeMainDicomTags(other, ResourceType_Study);
+    MergeMainDicomTags(other, ResourceType_Series);
+    MergeMainDicomTags(other, ResourceType_Instance);
   }    
 
 
--- a/Core/DicomFormat/DicomMap.h	Mon Feb 17 17:54:40 2020 +0100
+++ b/Core/DicomFormat/DicomMap.h	Thu Feb 20 20:36:47 2020 +0100
@@ -63,9 +63,6 @@
     static void GetMainDicomTagsInternal(std::set<DicomTag>& result,
                                          ResourceType level);
 
-    void ExtractMainDicomTagsInternal(const DicomMap& other,
-                                      ResourceType level);
-
   public:
     DicomMap()
     {
@@ -215,6 +212,9 @@
 
     void Merge(const DicomMap& other);
 
+    void MergeMainDicomTags(const DicomMap& other,
+                            ResourceType level);
+
     void ExtractMainDicomTags(const DicomMap& other);
 
     bool HasOnlyMainDicomTags() const;
--- a/NEWS	Mon Feb 17 17:54:40 2020 +0100
+++ b/NEWS	Thu Feb 20 20:36:47 2020 +0100
@@ -22,12 +22,23 @@
 * New sample plugin: "ConnectivityChecks"
 * New primitives to handle storage commitment SCP by plugins
 
+Lua
+---
+
+* New events:
+  - "OnDeletedPatient", "OnDeletedStudy", "OnDeletedSeries", "OnDeletedInstance":
+    triggered when a resource is deleted
+  - "OnUpdatedPatient", "OnUpdatedStudy", "OnUpdatedSeries", "OnUpdatedInstance":
+    triggered when an attachment or a metadata is updated
+
 Maintenance
 -----------
 
 * Support of MPEG4 transfer syntaxes in C-Store SCP
-* C-Find SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE).
+* C-FIND SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE).
   Therefore, the "ClearCanvas" and "Dcm4Chee" modality manufacturer have now been deprecated.
+* More strict C-FIND SCP wrt. the DICOM standard: Forbid wildcard
+  matching on some VRs, ignore main tags below the queried level
 * Fix issue #65 (Logging improvements)
 * Fix issue #156 (Chunked Dicom-web transfer uses 100% CPU)
 * Fix issue #165 (Boundary parameter in multipart Content-Type is too long)
--- a/OrthancServer/LuaScripting.cpp	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/LuaScripting.cpp	Thu Feb 20 20:36:47 2020 +0100
@@ -259,6 +259,114 @@
   };
 
 
+  class LuaScripting::DeleteEvent : public LuaScripting::IEvent
+  {
+  private:
+    ResourceType  level_;
+    std::string   publicId_;
+
+  public:
+    DeleteEvent(ResourceType level,
+                const std::string& publicId) :
+      level_(level),
+      publicId_(publicId)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      std::string functionName;
+      
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          functionName = "OnDeletedPatient";
+          break;
+
+        case ResourceType_Study:
+          functionName = "OnDeletedStudy";
+          break;
+
+        case ResourceType_Series:
+          functionName = "OnDeletedSeries";
+          break;
+
+        case ResourceType_Instance:
+          functionName = "OnDeletedInstance";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      {
+        LuaScripting::Lock lock(that);
+
+        if (lock.GetLua().IsExistingFunction(functionName.c_str()))
+        {
+          LuaFunctionCall call(lock.GetLua(), functionName.c_str());
+          call.PushString(publicId_);
+          call.Execute();
+        }
+      }
+    }
+  };
+
+
+  class LuaScripting::UpdateEvent : public LuaScripting::IEvent
+  {
+  private:
+    ResourceType  level_;
+    std::string   publicId_;
+
+  public:
+    UpdateEvent(ResourceType level,
+                const std::string& publicId) :
+      level_(level),
+      publicId_(publicId)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      std::string functionName;
+      
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          functionName = "OnUpdatedPatient";
+          break;
+
+        case ResourceType_Study:
+          functionName = "OnUpdatedStudy";
+          break;
+
+        case ResourceType_Series:
+          functionName = "OnUpdatedSeries";
+          break;
+
+        case ResourceType_Instance:
+          functionName = "OnUpdatedInstance";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      {
+        LuaScripting::Lock lock(that);
+
+        if (lock.GetLua().IsExistingFunction(functionName.c_str()))
+        {
+          LuaFunctionCall call(lock.GetLua(), functionName.c_str());
+          call.PushString(publicId_);
+          call.Execute();
+        }
+      }
+    }
+  };
+
+
   ServerContext* LuaScripting::GetServerContext(lua_State *state)
   {
     const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext");
@@ -738,6 +846,15 @@
     {
       pendingEvents_.Enqueue(new StableResourceEvent(change));
     }
+    else if (change.GetChangeType() == ChangeType_Deleted)
+    {
+      pendingEvents_.Enqueue(new DeleteEvent(change.GetResourceType(), change.GetPublicId()));
+    }
+    else if (change.GetChangeType() == ChangeType_UpdatedAttachment ||
+             change.GetChangeType() == ChangeType_UpdatedMetadata)
+    {
+      pendingEvents_.Enqueue(new UpdateEvent(change.GetResourceType(), change.GetPublicId()));
+    }
   }
 
 
--- a/OrthancServer/LuaScripting.h	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/LuaScripting.h	Thu Feb 20 20:36:47 2020 +0100
@@ -59,6 +59,8 @@
     class OnStoredInstanceEvent;
     class StableResourceEvent;
     class JobEvent;
+    class DeleteEvent;
+    class UpdateEvent;
 
     static ServerContext* GetServerContext(lua_State *state);
 
--- a/OrthancServer/Search/DatabaseLookup.cpp	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/Search/DatabaseLookup.cpp	Thu Feb 20 20:36:47 2020 +0100
@@ -198,8 +198,26 @@
 
       AddConstraint(constraint.release());
     }
-    else if (dicomQuery.find('*') != std::string::npos ||
-             dicomQuery.find('?') != std::string::npos)
+    else if (
+      /**
+       * New test in Orthanc 1.6.0: Wild card matching is only allowed
+       * for a subset of value representations: AE, CS, LO, LT, PN,
+       * SH, ST, UC, UR, UT.
+       * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.4.html
+       **/
+      (vr == ValueRepresentation_ApplicationEntity ||    // AE
+       vr == ValueRepresentation_CodeString ||           // CS
+       vr == ValueRepresentation_LongString ||           // LO
+       vr == ValueRepresentation_LongText ||             // LT
+       vr == ValueRepresentation_PersonName ||           // PN
+       vr == ValueRepresentation_ShortString ||          // SH
+       vr == ValueRepresentation_ShortText ||            // ST
+       vr == ValueRepresentation_UnlimitedCharacters ||  // UC
+       vr == ValueRepresentation_UniversalResource ||    // UR
+       vr == ValueRepresentation_UnlimitedText           // UT
+        ) &&
+      (dicomQuery.find('*') != std::string::npos ||
+       dicomQuery.find('?') != std::string::npos))
     {
       AddConstraint(new DicomTagConstraint
                     (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag));
--- a/OrthancServer/ServerContext.cpp	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/ServerContext.cpp	Thu Feb 20 20:36:47 2020 +0100
@@ -254,6 +254,11 @@
       jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
       saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
       metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+
+      // New configuration options in Orthanc 1.5.1
+      findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
+      limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
+      limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
     }
 
     jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -796,44 +801,9 @@
                             size_t since,
                             size_t limit)
   {
-    LookupMode mode;
-    unsigned int databaseLimit;
+    unsigned int databaseLimit = (queryLevel == ResourceType_Instance ?
+                                  limitFindInstances_ : limitFindResults_);
       
-    {
-      // New configuration option in 1.5.1
-      OrthancConfiguration::ReaderLock lock;
-
-      std::string value = lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always");
-
-      if (value == "Always")
-      {
-        mode = LookupMode_DiskOnLookupAndAnswer;
-      }
-      else if (value == "Never")
-      {
-        mode = LookupMode_DatabaseOnly;
-      }
-      else if (value == "Answers")
-      {
-        mode = LookupMode_DiskOnAnswer;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Configuration option \"StorageAccessOnFind\" "
-                               "should be \"Always\", \"Never\" or \"Answers\": " + value);
-      }
-
-      if (queryLevel == ResourceType_Instance)
-      {
-        databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
-      }
-      else
-      {
-        databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
-      }
-    }      
-
     std::vector<std::string> resources, instances;
 
     {
@@ -846,6 +816,11 @@
 
     LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size();
 
+    /**
+     * "resources" contains the Orthanc ID of the resource at level
+     * "queryLevel", "instances" contains one the Orthanc ID of one
+     * sample instance from this resource.
+     **/
     assert(resources.size() == instances.size());
 
     size_t countResults = 0;
@@ -863,19 +838,54 @@
       bool hasOnlyMainDicomTags;
       DicomMap dicom;
       
-      if (mode == LookupMode_DatabaseOnly ||
-          mode == LookupMode_DiskOnAnswer ||
+      if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly ||
+          findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer ||
           lookup.HasOnlyMainDicomTags())
       {
         // Case (1): The main DICOM tags, as stored in the database,
         // are sufficient to look for match
 
-        if (!GetIndex().GetAllMainDicomTags(dicom, instances[i]))
+        DicomMap tmp;
+        if (!GetIndex().GetAllMainDicomTags(tmp, instances[i]))
         {
           // The instance has been removed during the execution of the
           // lookup, ignore it
           continue;
         }
+
+#if 1
+        // New in Orthanc 1.6.0: Only keep the main DICOM tags at the
+        // level of interest for the query
+        switch (queryLevel)
+        {
+          // WARNING: Don't reorder cases below, and don't add "break"
+          case ResourceType_Instance:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Instance);
+
+          case ResourceType_Series:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Series);
+
+          case ResourceType_Study:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Study);
+            
+          case ResourceType_Patient:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Patient);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Special case of the "Modality" at the study level, in order
+        // to deal with C-FIND on "ModalitiesInStudy" (0008,0061).
+        // Check out integration test "test_rest_modalities_in_study".
+        if (queryLevel == ResourceType_Study)
+        {
+          dicom.CopyTagIfExists(tmp, DICOM_TAG_MODALITY);
+        }
+#else
+        dicom.Assign(tmp);  // This emulates Orthanc <= 1.5.8
+#endif
         
         hasOnlyMainDicomTags = true;
       }
@@ -907,8 +917,8 @@
         }
         else
         {
-          if ((mode == LookupMode_DiskOnLookupAndAnswer ||
-               mode == LookupMode_DiskOnAnswer) &&
+          if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer ||
+               findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) &&
               dicomAsJson.get() == NULL &&
               isDicomAsJsonNeeded)
           {
--- a/OrthancServer/ServerContext.h	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/ServerContext.h	Thu Feb 20 20:36:47 2020 +0100
@@ -85,14 +85,6 @@
     
     
   private:
-    enum LookupMode
-    {
-      LookupMode_DatabaseOnly,
-      LookupMode_DiskOnAnswer,
-      LookupMode_DiskOnLookupAndAnswer
-    };
-
-    
     class LuaServerListener : public IServerListener
     {
     private:
@@ -221,6 +213,9 @@
     std::string defaultLocalAet_;
     OrthancHttpHandler  httpHandler_;
     bool saveJobs_;
+    FindStorageAccessMode findStorageAccessMode_;
+    unsigned int limitFindInstances_;
+    unsigned int limitFindResults_;
 
     std::auto_ptr<MetricsRegistry>  metricsRegistry_;
     bool isHttpServerSecure_;
--- a/OrthancServer/ServerEnumerations.cpp	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Feb 20 20:36:47 2020 +0100
@@ -192,6 +192,30 @@
     return dictContentType_.Translate(str);
   }
 
+
+  FindStorageAccessMode StringToFindStorageAccessMode(const std::string& value)
+  {
+    if (value == "Always")
+    {
+      return FindStorageAccessMode_DiskOnLookupAndAnswer;
+    }
+    else if (value == "Never")
+    {
+      return FindStorageAccessMode_DatabaseOnly;
+    }
+    else if (value == "Answers")
+    {
+      return FindStorageAccessMode_DiskOnAnswer;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Configuration option \"StorageAccessOnFind\" "
+                             "should be \"Always\", \"Never\" or \"Answers\": " + value);
+    }    
+  }
+  
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId)
   {
--- a/OrthancServer/ServerEnumerations.h	Mon Feb 17 17:54:40 2020 +0100
+++ b/OrthancServer/ServerEnumerations.h	Thu Feb 20 20:36:47 2020 +0100
@@ -83,6 +83,13 @@
     };
   }
 
+  enum FindStorageAccessMode
+  {
+    FindStorageAccessMode_DatabaseOnly,
+    FindStorageAccessMode_DiskOnAnswer,
+    FindStorageAccessMode_DiskOnLookupAndAnswer
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -178,6 +185,8 @@
 
   FileContentType StringToContentType(const std::string& str);
 
+  FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str);
+
   std::string EnumerationToString(FileContentType type);
 
   std::string GetFileContentMime(FileContentType type);