changeset 2845:218e2c864d1d

serialization of SplitStudyJob
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 28 Sep 2018 17:59:44 +0200
parents 99863d6245b2
children d386abc18133
files Core/SerializationToolbox.cpp Core/SerializationToolbox.h OrthancServer/ServerJobs/OrthancJobUnserializer.cpp OrthancServer/ServerJobs/SplitStudyJob.cpp OrthancServer/ServerJobs/SplitStudyJob.h UnitTestsSources/MultiThreadingTests.cpp
diffstat 6 files changed, 276 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/Core/SerializationToolbox.cpp	Fri Sep 28 16:48:43 2018 +0200
+++ b/Core/SerializationToolbox.cpp	Fri Sep 28 17:59:44 2018 +0200
@@ -196,6 +196,75 @@
     }
 
 
+    void ReadMapOfStrings(std::map<std::string, std::string>& target,
+                          const Json::Value& value,
+                          const std::string& field)
+    {
+      if (value.type() != Json::objectValue ||
+          !value.isMember(field.c_str()) ||
+          value[field.c_str()].type() != Json::objectValue)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      const Json::Value& source = value[field.c_str()];
+
+      target.clear();
+
+      Json::Value::Members members = source.getMemberNames();
+
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        const Json::Value& tmp = source[members[i]];
+
+        if (tmp.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat);        
+        }
+        else
+        {
+          target[members[i]] = tmp.asString();
+        }
+      }
+    }
+
+
+    void ReadMapOfTags(std::map<DicomTag, std::string>& target,
+                       const Json::Value& value,
+                       const std::string& field)
+    {
+      if (value.type() != Json::objectValue ||
+          !value.isMember(field.c_str()) ||
+          value[field.c_str()].type() != Json::objectValue)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      const Json::Value& source = value[field.c_str()];
+
+      target.clear();
+
+      Json::Value::Members members = source.getMemberNames();
+
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        const Json::Value& tmp = source[members[i]];
+
+        DicomTag tag(0, 0);
+
+        if (!DicomTag::ParseHexadecimal(tag, members[i].c_str()) ||
+            tmp.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat);        
+        }
+        else
+        {
+          target[tag] = tmp.asString();
+        }
+      }
+    }
+
+
     void WriteArrayOfStrings(Json::Value& target,
                              const std::vector<std::string>& values,
                              const std::string& field)
@@ -258,5 +327,49 @@
         value.append(it->Format());
       }
     }
+
+
+    void WriteMapOfStrings(Json::Value& target,
+                           const std::map<std::string, std::string>& values,
+                           const std::string& field)
+    {
+      if (target.type() != Json::objectValue ||
+          target.isMember(field.c_str()))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      Json::Value& value = target[field];
+
+      value = Json::objectValue;
+
+      for (std::map<std::string, std::string>::const_iterator
+             it = values.begin(); it != values.end(); ++it)
+      {
+        value[it->first] = it->second;
+      }
+    }
+
+
+    void WriteMapOfTags(Json::Value& target,
+                        const std::map<DicomTag, std::string>& values,
+                        const std::string& field)
+    {
+      if (target.type() != Json::objectValue ||
+          target.isMember(field.c_str()))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      Json::Value& value = target[field];
+
+      value = Json::objectValue;
+
+      for (std::map<DicomTag, std::string>::const_iterator
+             it = values.begin(); it != values.end(); ++it)
+      {
+        value[it->first.Format()] = it->second;
+      }
+    }
   }
 }
--- a/Core/SerializationToolbox.h	Fri Sep 28 16:48:43 2018 +0200
+++ b/Core/SerializationToolbox.h	Fri Sep 28 17:59:44 2018 +0200
@@ -37,6 +37,7 @@
 
 #include <json/value.h>
 #include <list>
+#include <map>
 
 namespace Orthanc
 {
@@ -70,6 +71,14 @@
                        const Json::Value& value,
                        const std::string& field);
 
+    void ReadMapOfStrings(std::map<std::string, std::string>& values,
+                          const Json::Value& target,
+                          const std::string& field);
+
+    void ReadMapOfTags(std::map<DicomTag, std::string>& values,
+                       const Json::Value& target,
+                       const std::string& field);
+
     void WriteArrayOfStrings(Json::Value& target,
                              const std::vector<std::string>& values,
                              const std::string& field);
@@ -81,5 +90,13 @@
     void WriteSetOfTags(Json::Value& target,
                         const std::set<DicomTag>& tags,
                         const std::string& field);
+
+    void WriteMapOfStrings(Json::Value& target,
+                           const std::map<std::string, std::string>& values,
+                           const std::string& field);
+
+    void WriteMapOfTags(Json::Value& target,
+                        const std::map<DicomTag, std::string>& values,
+                        const std::string& field);
   }
 }
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Fri Sep 28 16:48:43 2018 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Fri Sep 28 17:59:44 2018 +0200
@@ -48,6 +48,7 @@
 #include "DicomModalityStoreJob.h"
 #include "OrthancPeerStoreJob.h"
 #include "ResourceModificationJob.h"
+#include "SplitStudyJob.h"
 
 namespace Orthanc
 {
@@ -78,6 +79,10 @@
     {
       return new ResourceModificationJob(context_, source);
     }
+    else if (type == "SplitStudy")
+    {
+      return new SplitStudyJob(context_, source);
+    }
     else
     {
       return GenericJobUnserializer::UnserializeJob(source);
--- a/OrthancServer/ServerJobs/SplitStudyJob.cpp	Fri Sep 28 16:48:43 2018 +0200
+++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp	Fri Sep 28 17:59:44 2018 +0200
@@ -33,8 +33,9 @@
 
 #include "SplitStudyJob.h"
 
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/Logging.h"
-#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/SerializationToolbox.h"
 
 namespace Orthanc
 {
@@ -50,36 +51,14 @@
   }
 
   
-  void SplitStudyJob::Setup(const std::string& sourceStudy)
+  void SplitStudyJob::Setup()
   {
     SetPermissive(false);
     
-    keepSource_ = false;
-    sourceStudy_ = sourceStudy;
-    targetStudyUid_ = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study);
-    
     DicomTag::AddTagsForModule(allowedTags_, DicomModule_Patient);
     DicomTag::AddTagsForModule(allowedTags_, DicomModule_Study);
     allowedTags_.erase(DICOM_TAG_STUDY_INSTANCE_UID);
     allowedTags_.erase(DICOM_TAG_SERIES_INSTANCE_UID);
-
-    ResourceType type;
-    
-    if (!context_.GetIndex().LookupResourceType(type, sourceStudy) ||
-        type != ResourceType_Study)
-    {
-      LOG(ERROR) << "Cannot split unknown study: " << sourceStudy;
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    std::list<std::string> children;
-    context_.GetIndex().GetChildren(children, sourceStudy);
-
-    for (std::list<std::string>::const_iterator
-           it = children.begin(); it != children.end(); ++it)
-    {
-      sourceSeries_.insert(*it);
-    }
   }
 
   
@@ -132,7 +111,7 @@
      * Apply user-specified modifications
      **/
 
-    for (Removals::const_iterator it = removals_.begin();
+    for (std::set<DicomTag>::const_iterator it = removals_.begin();
          it != removals_.end(); ++it)
     {
       modified->Remove(*it);
@@ -192,22 +171,33 @@
   SplitStudyJob::SplitStudyJob(ServerContext& context,
                                const std::string& sourceStudy) :
     SetOfInstancesJob(true /* with trailing step */),
-    context_(context)
+    context_(context),
+    keepSource_(false),
+    sourceStudy_(sourceStudy),
+    targetStudyUid_(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study))
   {
-    Setup(sourceStudy);
+    Setup();
+    
+    ResourceType type;
+    
+    if (!context_.GetIndex().LookupResourceType(type, sourceStudy) ||
+        type != ResourceType_Study)
+    {
+      LOG(ERROR) << "Cannot split unknown study: " << sourceStudy;
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    std::list<std::string> children;
+    context_.GetIndex().GetChildren(children, sourceStudy);
+
+    for (std::list<std::string>::const_iterator
+           it = children.begin(); it != children.end(); ++it)
+    {
+      sourceSeries_.insert(*it);
+    }
   }
   
 
-  SplitStudyJob::SplitStudyJob(ServerContext& context,
-                               const Json::Value& serialized) :
-    SetOfInstancesJob(serialized),
-    context_(context)
-  {
-    //assert(HasTrailingStep());
-    //Setup();
-  }
-
-  
   void SplitStudyJob::SetOrigin(const DicomInstanceOrigin& origin)
   {
     if (IsStarted())
@@ -300,10 +290,63 @@
     
     value["TargetStudyUID"] = targetStudyUid_;
   }
+
+
+  static const char* SOURCE_STUDY = "SourceStudy";
+  static const char* SOURCE_SERIES = "SourceSeries";
+  static const char* KEEP_SOURCE = "KeepSource";
+  static const char* TARGET_STUDY = "TargetStudy";
+  static const char* TARGET_STUDY_UID = "TargetStudyUID";
+  static const char* TARGET_SERIES = "TargetSeries";
+  static const char* ORIGIN = "Origin";
+  static const char* REPLACEMENTS = "Replacements";
+  static const char* REMOVALS = "Removals";
+
+
+  SplitStudyJob::SplitStudyJob(ServerContext& context,
+                               const Json::Value& serialized) :
+    SetOfInstancesJob(serialized),  // (*)
+    context_(context)
+  {
+    if (!HasTrailingStep())
+    {
+      // Should have been set by (*)
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    Setup();
+
+    keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE);
+    sourceStudy_ = SerializationToolbox::ReadString(serialized, SOURCE_STUDY);
+    SerializationToolbox::ReadSetOfStrings(sourceSeries_, serialized, SOURCE_SERIES);
+    targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY);
+    targetStudyUid_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY_UID);
+    SerializationToolbox::ReadMapOfStrings(targetSeries_, serialized, TARGET_SERIES);
+    origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
+    SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS);
+    SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS);
+  }
+
   
-
   bool SplitStudyJob::Serialize(Json::Value& target)
   {
-    return true;
+    if (!SetOfInstancesJob::Serialize(target))
+    {
+      return false;
+    }
+    else
+    {
+      target[KEEP_SOURCE] = keepSource_;
+      target[SOURCE_STUDY] = sourceStudy_;
+      SerializationToolbox::WriteSetOfStrings(target, sourceSeries_, SOURCE_SERIES);
+      target[TARGET_STUDY] = targetStudy_;
+      target[TARGET_STUDY_UID] = targetStudyUid_;
+      SerializationToolbox::WriteMapOfStrings(target, targetSeries_, TARGET_SERIES);
+      origin_.Serialize(target[ORIGIN]);
+      SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS);
+      SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS);
+
+      return true;
+    }
   }
 }
--- a/OrthancServer/ServerJobs/SplitStudyJob.h	Fri Sep 28 16:48:43 2018 +0200
+++ b/OrthancServer/ServerJobs/SplitStudyJob.h	Fri Sep 28 17:59:44 2018 +0200
@@ -44,24 +44,23 @@
   private:
     typedef std::map<std::string, std::string>  SeriesUidMap;
     typedef std::map<DicomTag, std::string>     Replacements;
-    typedef std::set<DicomTag>                  Removals;
     
     
     ServerContext&         context_;
+    bool                   keepSource_;
     std::string            sourceStudy_;
     std::set<std::string>  sourceSeries_;
-    bool                   keepSource_;
     std::string            targetStudy_;
     std::string            targetStudyUid_;
     SeriesUidMap           targetSeries_;
     std::set<DicomTag>     allowedTags_;
     DicomInstanceOrigin    origin_;
     Replacements           replacements_;
-    Removals               removals_;
+    std::set<DicomTag>     removals_;
 
     void CheckAllowedTag(const DicomTag& tag) const;
     
-    void Setup(const std::string& sourceStudy);
+    void Setup();
     
   protected:
     virtual bool HandleInstance(const std::string& instance);
@@ -75,9 +74,10 @@
     SplitStudyJob(ServerContext& context,
                   const Json::Value& serialized);
 
-    void SetOrigin(const DicomInstanceOrigin& origin);
-
-    void SetOrigin(const RestApiCall& call);
+    const std::string& GetSourceStudy() const
+    {
+      return sourceStudy_;
+    }
 
     void AddSourceSeries(const std::string& series);
 
@@ -88,18 +88,27 @@
     
     void SetKeepSource(bool keep);
 
-    void Remove(const DicomTag& tag);
-    
     void Replace(const DicomTag& tag,
                  const std::string& value);
     
+    void Remove(const DicomTag& tag);
+    
+    void SetOrigin(const DicomInstanceOrigin& origin);
+
+    void SetOrigin(const RestApiCall& call);
+
+    const DicomInstanceOrigin& GetOrigin() const
+    {
+      return origin_;
+    }
+
     virtual void Stop(JobStopReason reason)
     {
     }
 
     virtual void GetJobType(std::string& target)
     {
-      target = "SplitStudyJob";
+      target = "SplitStudy";
     }
 
     virtual void GetPublicContent(Json::Value& value);
--- a/UnitTestsSources/MultiThreadingTests.cpp	Fri Sep 28 16:48:43 2018 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Fri Sep 28 17:59:44 2018 +0200
@@ -63,6 +63,7 @@
 #include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h"
 #include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h"
 #include "../OrthancServer/ServerJobs/ResourceModificationJob.h"
+#include "../OrthancServer/ServerJobs/SplitStudyJob.h"
 
 
 using namespace Orthanc;
@@ -1463,9 +1464,9 @@
 
 TEST_F(OrthancJobsSerialization, Jobs)
 {
-  // ArchiveJob
+  Json::Value s;
 
-  Json::Value s;
+  // ArchiveJob
 
   {
     boost::shared_ptr<TemporaryFile> tmp(new TemporaryFile);
@@ -1557,6 +1558,43 @@
     ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
     ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
   }
+
+  // SplitStudyJob
+
+  std::string id;
+  ASSERT_TRUE(CreateInstance(id));
+
+  std::string study, series;
+
+  {
+    ServerContext::DicomCacheLocker lock(GetContext(), id);
+    study = lock.GetDicom().GetHasher().HashStudy();
+    series = lock.GetDicom().GetHasher().HashSeries();
+  }
+
+  {
+    ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException);
+
+    SplitStudyJob job(GetContext(), study);
+    job.SetKeepSource(true);
+    job.AddSourceSeries(series);
+    ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException);
+    job.SetOrigin(DicomInstanceOrigin::FromLua());
+    
+    ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
+    ASSERT_TRUE(job.Serialize(s));
+  }
+
+  {
+    std::auto_ptr<IJob> job;
+    job.reset(unserializer.UnserializeJob(s));
+
+    SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job);
+    ASSERT_TRUE(tmp.IsKeepSource());
+    ASSERT_EQ(study, tmp.GetSourceStudy());
+    ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin());
+    //ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION));
+  }
 }