changeset 2655:c196d76cb8fa jobs

serialization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 05 Jun 2018 17:57:49 +0200
parents 761031029aa9
children a6d3e45eeff5
files Core/DicomNetworking/RemoteModalityParameters.cpp Core/DicomNetworking/RemoteModalityParameters.h Core/DicomParsing/DicomModification.cpp Core/DicomParsing/DicomModification.h Core/Enumerations.cpp Core/Enumerations.h Core/JobsEngine/GenericJobUnserializer.cpp Core/JobsEngine/IJobUnserializer.cpp Core/JobsEngine/IJobUnserializer.h Core/JobsEngine/JobsRegistry.cpp Core/JobsEngine/Operations/JobOperationValues.cpp Core/JobsEngine/Operations/LogJobOperation.h Core/JobsEngine/Operations/NullOperationValue.h Core/JobsEngine/Operations/StringOperationValue.h Core/JobsEngine/SetOfInstancesJob.cpp Core/WebServiceParameters.cpp Core/WebServiceParameters.h OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp OrthancServer/ServerJobs/Operations/StorePeerOperation.h OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp OrthancServer/ServerJobs/Operations/StoreScuOperation.h OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp OrthancServer/ServerJobs/Operations/SystemCallOperation.h OrthancServer/ServerJobs/OrthancJobUnserializer.cpp UnitTestsSources/MultiThreadingTests.cpp UnitTestsSources/SQLiteChromiumTests.cpp
diffstat 29 files changed, 501 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -125,4 +125,17 @@
     value.append(GetPort());
     value.append(EnumerationToString(GetManufacturer()));
   }
+
+
+  
+  void RemoteModalityParameters::Serialize(Json::Value& target) const
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+  
+  RemoteModalityParameters::RemoteModalityParameters(const Json::Value& serialized)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
 }
--- a/Core/DicomNetworking/RemoteModalityParameters.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/DicomNetworking/RemoteModalityParameters.h	Tue Jun 05 17:57:49 2018 +0200
@@ -52,6 +52,8 @@
   public:
     RemoteModalityParameters();
 
+    RemoteModalityParameters(const Json::Value& serialized);
+
     RemoteModalityParameters(const std::string& aet,
                              const std::string& host,
                              uint16_t port,
@@ -105,5 +107,7 @@
     void FromJson(const Json::Value& modality);
 
     void ToJson(Json::Value& value) const;
+
+    void Serialize(Json::Value& target) const;
   };
 }
--- a/Core/DicomParsing/DicomModification.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/DicomParsing/DicomModification.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -1241,7 +1241,12 @@
   
   void DicomModification::Serialize(Json::Value& value) const
   {
-    // TODO
-    value = "TODO";
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+  
+  DicomModification::DicomModification(const Json::Value& serialized)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
   }
 }
--- a/Core/DicomParsing/DicomModification.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/DicomParsing/DicomModification.h	Tue Jun 05 17:57:49 2018 +0200
@@ -110,6 +110,8 @@
   public:
     DicomModification();
 
+    DicomModification(const Json::Value& serialized);
+
     ~DicomModification();
 
     void Keep(const DicomTag& tag);
--- a/Core/Enumerations.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/Enumerations.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -1500,6 +1500,35 @@
   }
 
 
+  RequestOrigin StringToRequestOrigin(const std::string& origin)
+  {
+    if (origin == "Unknown")
+    {
+      return RequestOrigin_Unknown;
+    }
+    else if (origin == "DicomProtocol")
+    {
+      return RequestOrigin_DicomProtocol;
+    }
+    else if (origin == "RestApi")
+    {
+      return RequestOrigin_RestApi;
+    }
+    else if (origin == "Plugins")
+    {
+      return RequestOrigin_Plugins;
+    }
+    else if (origin == "Lua")
+    {
+      return RequestOrigin_Lua;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+  
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
--- a/Core/Enumerations.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/Enumerations.h	Tue Jun 05 17:57:49 2018 +0200
@@ -668,6 +668,8 @@
 
   JobState StringToJobState(const std::string& state);
   
+  RequestOrigin StringToRequestOrigin(const std::string& origin);
+  
   unsigned int GetBytesPerPixel(PixelFormat format);
 
   bool GetDicomEncoding(Encoding& encoding,
--- a/Core/JobsEngine/GenericJobUnserializer.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/GenericJobUnserializer.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -45,7 +45,7 @@
 {
   IJob* GenericJobUnserializer::UnserializeJob(const Json::Value& source)
   {
-    const std::string type = GetString(source, "Type");
+    const std::string type = ReadString(source, "Type");
 
     LOG(ERROR) << "Cannot unserialize job of type: " << type;
     throw OrthancException(ErrorCode_BadFileFormat);
@@ -54,7 +54,7 @@
 
   IJobOperation* GenericJobUnserializer::UnserializeOperation(const Json::Value& source)
   {
-    const std::string type = GetString(source, "Type");
+    const std::string type = ReadString(source, "Type");
 
     if (type == "Log")
     {
@@ -70,11 +70,11 @@
 
   JobOperationValue* GenericJobUnserializer::UnserializeValue(const Json::Value& source)
   {
-    const std::string type = GetString(source, "Type");
+    const std::string type = ReadString(source, "Type");
 
     if (type == "String")
     {
-      return new StringOperationValue(GetString(source, "Content"));
+      return new StringOperationValue(ReadString(source, "Content"));
     }
     else if (type == "Null")
     {
--- a/Core/JobsEngine/IJobUnserializer.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/IJobUnserializer.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -38,43 +38,43 @@
 
 namespace Orthanc
 {
-  std::string IJobUnserializer::GetString(const Json::Value& value,
-                                          const std::string& name)
+  std::string IJobUnserializer::ReadString(const Json::Value& value,
+                                           const std::string& field)
   {
     if (value.type() != Json::objectValue ||
-        !value.isMember(name.c_str()) ||
-        value[name.c_str()].type() != Json::stringValue)
+        !value.isMember(field.c_str()) ||
+        value[field.c_str()].type() != Json::stringValue)
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
     else
     {
-      return value[name.c_str()].asString();
+      return value[field.c_str()].asString();
     }
   }
 
 
-  int IJobUnserializer::GetInteger(const Json::Value& value,
-                                   const std::string& name)
+  int IJobUnserializer::ReadInteger(const Json::Value& value,
+                                    const std::string& field)
   {
     if (value.type() != Json::objectValue ||
-        !value.isMember(name.c_str()) ||
-        (value[name.c_str()].type() != Json::intValue &&
-         value[name.c_str()].type() != Json::uintValue))
+        !value.isMember(field.c_str()) ||
+        (value[field.c_str()].type() != Json::intValue &&
+         value[field.c_str()].type() != Json::uintValue))
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
     else
     {
-      return value[name.c_str()].asInt();
+      return value[field.c_str()].asInt();
     }    
   }
 
 
-  unsigned int IJobUnserializer::GetUnsignedInteger(const Json::Value& value,
-                                                    const std::string& name)
+  unsigned int IJobUnserializer::ReadUnsignedInteger(const Json::Value& value,
+                                                     const std::string& field)
   {
-    int tmp = GetInteger(value, name);
+    int tmp = ReadInteger(value, field);
 
     if (tmp < 0)
     {
@@ -87,29 +87,29 @@
   }
 
 
-  bool IJobUnserializer::GetBoolean(const Json::Value& value,
-                                    const std::string& name)
+  bool IJobUnserializer::ReadBoolean(const Json::Value& value,
+                                     const std::string& field)
   {
     if (value.type() != Json::objectValue ||
-        !value.isMember(name.c_str()) ||
-        value[name.c_str()].type() != Json::booleanValue)
+        !value.isMember(field.c_str()) ||
+        value[field.c_str()].type() != Json::booleanValue)
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
     else
     {
-      return value[name.c_str()].asBool();
+      return value[field.c_str()].asBool();
     }   
   }
 
   
-  void IJobUnserializer::GetArrayOfStrings(std::vector<std::string>& target,
-                                           const Json::Value& value,
-                                           const std::string& name)
+  void IJobUnserializer::ReadArrayOfStrings(std::vector<std::string>& target,
+                                            const Json::Value& value,
+                                            const std::string& field)
   {
     if (value.type() != Json::objectValue ||
-        !value.isMember(name.c_str()) ||
-        value[name.c_str()].type() != Json::arrayValue)
+        !value.isMember(field.c_str()) ||
+        value[field.c_str()].type() != Json::arrayValue)
     {
       throw OrthancException(ErrorCode_BadFileFormat);
     }
@@ -117,7 +117,7 @@
     target.clear();
     target.resize(value.size());
 
-    const Json::Value arr = value[name.c_str()];
+    const Json::Value arr = value[field.c_str()];
     
     for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++)
     {
@@ -133,12 +133,12 @@
   }
 
 
-  void IJobUnserializer::GetListOfStrings(std::list<std::string>& target,
-                                          const Json::Value& value,
-                                          const std::string& name)
+  void IJobUnserializer::ReadListOfStrings(std::list<std::string>& target,
+                                           const Json::Value& value,
+                                           const std::string& field)
   {
     std::vector<std::string> tmp;
-    GetArrayOfStrings(tmp, value, name);
+    ReadArrayOfStrings(tmp, value, field);
 
     target.clear();
     for (size_t i = 0; i < tmp.size(); i++)
@@ -148,12 +148,12 @@
   }
   
 
-  void IJobUnserializer::GetSetOfStrings(std::set<std::string>& target,
-                                         const Json::Value& value,
-                                         const std::string& name)
+  void IJobUnserializer::ReadSetOfStrings(std::set<std::string>& target,
+                                          const Json::Value& value,
+                                          const std::string& field)
   {
     std::vector<std::string> tmp;
-    GetArrayOfStrings(tmp, value, name);
+    ReadArrayOfStrings(tmp, value, field);
 
     target.clear();
     for (size_t i = 0; i < tmp.size(); i++)
@@ -161,4 +161,26 @@
       target.insert(tmp[i]);
     }
   }
+
+
+  void IJobUnserializer::WriteArrayOfStrings(Json::Value& target,
+                                             const std::vector<std::string>& values,
+                                             const std::string& field)
+  {
+    if (target.type() != Json::objectValue ||
+        target.isMember(field.c_str()))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    Json::Value tmp;
+
+    tmp = Json::arrayValue;
+    for (size_t i = 0; i < values.size(); i++)
+    {
+      tmp.append(values[i]);
+    }
+
+    target[field] = tmp;
+  }
 }
--- a/Core/JobsEngine/IJobUnserializer.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/IJobUnserializer.h	Tue Jun 05 17:57:49 2018 +0200
@@ -54,28 +54,32 @@
 
     virtual JobOperationValue* UnserializeValue(const Json::Value& value) = 0;
 
-    static std::string GetString(const Json::Value& value,
-                                 const std::string& name);
+    static std::string ReadString(const Json::Value& value,
+                                  const std::string& field);
 
-    static int GetInteger(const Json::Value& value,
-                          const std::string& name);
+    static int ReadInteger(const Json::Value& value,
+                           const std::string& field);
 
-    static unsigned int GetUnsignedInteger(const Json::Value& value,
-                                           const std::string& name);
+    static unsigned int ReadUnsignedInteger(const Json::Value& value,
+                                            const std::string& field);
 
-    static bool GetBoolean(const Json::Value& value,
-                           const std::string& name);
+    static bool ReadBoolean(const Json::Value& value,
+                            const std::string& field);
 
-    static void GetArrayOfStrings(std::vector<std::string>& target,
+    static void ReadArrayOfStrings(std::vector<std::string>& target,
+                                   const Json::Value& value,
+                                   const std::string& field);
+
+    static void ReadListOfStrings(std::list<std::string>& target,
                                   const Json::Value& value,
-                                  const std::string& name);
+                                  const std::string& field);
 
-    static void GetListOfStrings(std::list<std::string>& target,
+    static void ReadSetOfStrings(std::set<std::string>& target,
                                  const Json::Value& value,
-                                 const std::string& name);
+                                 const std::string& field);
 
-    static void GetSetOfStrings(std::set<std::string>& target,
-                                const Json::Value& value,
-                                const std::string& name);
+    static void WriteArrayOfStrings(Json::Value& target,
+                                    const std::vector<std::string>& values,
+                                    const std::string& field);
   };
 }
--- a/Core/JobsEngine/JobsRegistry.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/JobsRegistry.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -236,6 +236,8 @@
 
     void Serialize(Json::Value& target) const
     {
+      target = Json::objectValue;
+      target["ID"] = id_;
       target["State"] = EnumerationToString(state_);
       target["JobType"] = jobType_;
       target["Priority"] = priority_;
@@ -258,18 +260,17 @@
     }
 
     JobHandler(IJobUnserializer& unserializer,
-               const std::string& id,
                const Json::Value& serialized) :
-      id_(id),
       lastStateChangeTime_(boost::posix_time::microsec_clock::universal_time()),
       pauseScheduled_(false),
       cancelScheduled_(false)
     {
-      state_ = StringToJobState(IJobUnserializer::GetString(serialized, "State"));
-      priority_ = IJobUnserializer::GetInteger(serialized, "Priority");
+      id_ = StringToJobState(IJobUnserializer::ReadString(serialized, "ID"));
+      state_ = StringToJobState(IJobUnserializer::ReadString(serialized, "State"));
+      priority_ = IJobUnserializer::ReadInteger(serialized, "Priority");
       creationTime_ = boost::posix_time::from_iso_string
-        (IJobUnserializer::GetString(serialized, "CreationTime"));
-      runtime_ = boost::posix_time::milliseconds(IJobUnserializer::GetInteger(serialized, "Runtime"));
+        (IJobUnserializer::ReadString(serialized, "CreationTime"));
+      runtime_ = boost::posix_time::milliseconds(IJobUnserializer::ReadInteger(serialized, "Runtime"));
 
       retryTime_ = creationTime_;
 
@@ -560,13 +561,14 @@
     boost::mutex::scoped_lock lock(mutex_);
     CheckInvariants();
 
-    target = Json::objectValue;
+    target = Json::arrayValue;
 
     for (JobsIndex::const_iterator it = jobsIndex_.begin(); 
          it != jobsIndex_.end(); ++it)
     {
-      Json::Value& v = target[it->first];
+      Json::Value v;
       it->second->Serialize(v);
+      target.append(v);
     }
   }
 
--- a/Core/JobsEngine/Operations/JobOperationValues.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/Operations/JobOperationValues.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -121,8 +121,8 @@
   }
 
 
-  JobOperationValues* Unserialize(IJobUnserializer& unserializer,
-                                  const Json::Value& source)
+  JobOperationValues* JobOperationValues::Unserialize(IJobUnserializer& unserializer,
+                                                      const Json::Value& source)
   {
     if (source.type() != Json::arrayValue)
     {
--- a/Core/JobsEngine/Operations/LogJobOperation.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.h	Tue Jun 05 17:57:49 2018 +0200
@@ -46,6 +46,7 @@
 
     virtual void Serialize(Json::Value& result) const
     {
+      result = Json::objectValue;
       result["Type"] = "Log";
     }
   };
--- a/Core/JobsEngine/Operations/NullOperationValue.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/Operations/NullOperationValue.h	Tue Jun 05 17:57:49 2018 +0200
@@ -52,6 +52,7 @@
 
     virtual void Serialize(Json::Value& target) const
     {
+      target = Json::objectValue;
       target["Type"] = "Null";
     }
   };
--- a/Core/JobsEngine/Operations/StringOperationValue.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/Operations/StringOperationValue.h	Tue Jun 05 17:57:49 2018 +0200
@@ -63,6 +63,7 @@
 
     virtual void Serialize(Json::Value& target) const
     {
+      target = Json::objectValue;
       target["Type"] = "String";
       target["Content"] = content_;
     }
--- a/Core/JobsEngine/SetOfInstancesJob.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/JobsEngine/SetOfInstancesJob.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -189,6 +189,8 @@
 
   void SetOfInstancesJob::Serialize(Json::Value& value)
   {
+    value = Json::objectValue;
+
     std::string type;
     GetJobType(type);
     value["Type"] = type;
@@ -220,12 +222,12 @@
 
   SetOfInstancesJob::SetOfInstancesJob(const Json::Value& value) :
     started_(false),
-    permissive_(IJobUnserializer::GetBoolean(value, "Permissive")),
-    position_(IJobUnserializer::GetUnsignedInteger(value, "Position")),
-    description_(IJobUnserializer::GetString(value, "Description"))
+    permissive_(IJobUnserializer::ReadBoolean(value, "Permissive")),
+    position_(IJobUnserializer::ReadUnsignedInteger(value, "Position")),
+    description_(IJobUnserializer::ReadString(value, "Description"))
   {
-    IJobUnserializer::GetArrayOfStrings(instances_, value, "Instances");
-    IJobUnserializer::GetSetOfStrings(failedInstances_, value, "FailedInstances");
+    IJobUnserializer::ReadArrayOfStrings(instances_, value, "Instances");
+    IJobUnserializer::ReadSetOfStrings(failedInstances_, value, "FailedInstances");
 
     if (position_ > instances_.size())
     {
--- a/Core/WebServiceParameters.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/WebServiceParameters.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -270,4 +270,16 @@
       }
     }
   }
+
+  
+  void WebServiceParameters::Serialize(Json::Value& target) const
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+  
+  WebServiceParameters::WebServiceParameters(const Json::Value& serialized)
+  {
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
 }
--- a/Core/WebServiceParameters.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/Core/WebServiceParameters.h	Tue Jun 05 17:57:49 2018 +0200
@@ -61,6 +61,8 @@
   public:
     WebServiceParameters();
 
+    WebServiceParameters(const Json::Value& serialized);
+
     const std::string& GetUrl() const
     {
       return url_;
@@ -127,5 +129,7 @@
     void FromJson(const Json::Value& peer);
 
     void ToJson(Json::Value& value) const;
+
+    void Serialize(Json::Value& target) const;
   };
 }
--- a/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h	Tue Jun 05 17:57:49 2018 +0200
@@ -76,6 +76,7 @@
 
     virtual void Serialize(Json::Value& target) const
     {
+      target = Json::objectValue;
       target["Type"] = "DicomInstance";
       target["ID"] = id_;
     }
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -127,10 +127,26 @@
 
   void ModifyInstanceOperation::Serialize(Json::Value& target) const
   {
-    result = Json::objectValue;
+    target = Json::objectValue;
     target["Type"] = "ModifyInstance";
     target["Origin"] = EnumerationToString(origin_);
     modification_->Serialize(target["Modification"]);
   }
+
+
+  ModifyInstanceOperation::ModifyInstanceOperation(ServerContext& context,
+                                                   const Json::Value& serialized) :
+    context_(context)
+  {
+    if (IJobUnserializer::ReadString(serialized, "Type") != "ModifyInstance" ||
+        !serialized.isMember("Modification"))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    origin_ = StringToRequestOrigin(IJobUnserializer::ReadString(serialized, "Origin"));
+
+    modification_.reset(new DicomModification(serialized["Modification"]));
+  }
 }
 
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Tue Jun 05 17:57:49 2018 +0200
@@ -52,6 +52,9 @@
                             RequestOrigin origin,
                             DicomModification* modification);  // Takes ownership
 
+    ModifyInstanceOperation(ServerContext& context,
+                            const Json::Value& serialized);
+
     const DicomModification& GetModification() const
     {
       return *modification_;
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -86,6 +86,18 @@
   {
     result = Json::objectValue;
     result["Type"] = "StorePeer";
-    peer_.ToJson(result["Remote"]);
+    peer_.Serialize(result["Peer"]);
+  }
+
+
+  StorePeerOperation::StorePeerOperation(const Json::Value& serialized)
+  {
+    if (IJobUnserializer::ReadString(serialized, "Type") != "StorePeer" ||
+        !serialized.isMember("Peer"))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    peer_ = WebServiceParameters(serialized["Peer"]);
   }
 }
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Tue Jun 05 17:57:49 2018 +0200
@@ -49,6 +49,8 @@
     {
     }
 
+    StorePeerOperation(const Json::Value& serialized);
+
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -86,6 +86,18 @@
     result = Json::objectValue;
     result["Type"] = "StoreScu";
     result["LocalAET"] = localAet_;
-    modality_.ToJson(result["Modality"]);
+    modality_.Serialize(result["Modality"]);
+  }
+
+
+  StoreScuOperation::StoreScuOperation(const Json::Value& serialized)
+  {
+    if (IJobUnserializer::ReadString(serialized, "Type") != "StoreScu" ||
+        !serialized.isMember("LocalAET"))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    modality_ = RemoteModalityParameters(serialized["Modality"]);
   }
 }
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Tue Jun 05 17:57:49 2018 +0200
@@ -52,6 +52,8 @@
     {
     }
 
+    StoreScuOperation(const Json::Value& serialized);
+
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& manager);
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -118,24 +118,20 @@
     result = Json::objectValue;
     result["Type"] = "SystemCall";
     result["Command"] = command_;
+    IJobUnserializer::WriteArrayOfStrings(result, preArguments_, "PreArguments");
+    IJobUnserializer::WriteArrayOfStrings(result, postArguments_, "PostArguments");
+  }
 
-    Json::Value tmp;
 
-    tmp = Json::arrayValue;
-    for (size_t i = 0; i < preArguments_.size(); i++)
+  SystemCallOperation::SystemCallOperation(const Json::Value& serialized)
+  {
+    if (IJobUnserializer::ReadString(serialized, "Type") != "SystemCall")
     {
-      tmp.append(preArguments_[i]);
+      throw OrthancException(ErrorCode_BadFileFormat);
     }
 
-    result["PreArguments"] = tmp;
-
-    tmp = Json::arrayValue;
-    for (size_t i = 0; i < postArguments_.size(); i++)
-    {
-      tmp.append(postArguments_[i]);
-    }
-
-    result["PostArguments"] = tmp;
+    command_ = IJobUnserializer::ReadString(serialized, "Command");
+    IJobUnserializer::ReadArrayOfStrings(preArguments_, serialized, "PreArguments");
+    IJobUnserializer::ReadArrayOfStrings(postArguments_, serialized, "PostArguments");
   }
 }
-
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Tue Jun 05 17:57:49 2018 +0200
@@ -52,6 +52,8 @@
     {
     }
 
+    SystemCallOperation(const Json::Value& serialized);
+
     SystemCallOperation(const std::string& command,
                         const std::vector<std::string>& preArguments,
                         const std::vector<std::string>& postArguments) :
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -37,7 +37,12 @@
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 
+#include "Operations/DeleteResourceOperation.h"
 #include "Operations/DicomInstanceOperationValue.h"
+#include "Operations/ModifyInstanceOperation.h"
+#include "Operations/StorePeerOperation.h"
+#include "Operations/StoreScuOperation.h"
+#include "Operations/SystemCallOperation.h"
 
 namespace Orthanc
 {
@@ -49,19 +54,42 @@
 
   IJobOperation* OrthancJobUnserializer::UnserializeOperation(const Json::Value& source)
   {
-    const std::string type = GetString(source, "Type");
+    const std::string type = ReadString(source, "Type");
 
-    return GenericJobUnserializer::UnserializeOperation(source);
+    if (type == "DeleteResource")
+    {
+      return new DeleteResourceOperation(context_);
+    }
+    else if (type == "ModifyInstance")
+    {
+      return new ModifyInstanceOperation(context_, source);
+    }
+    else if (type == "StorePeer")
+    {
+      return new StorePeerOperation(source);
+    }
+    else if (type == "StoreScu")
+    {
+      return new StoreScuOperation(source);
+    }
+    else if (type == "SystemCall")
+    {
+      return new SystemCallOperation(source);
+    }
+    else
+    {
+      return GenericJobUnserializer::UnserializeOperation(source);
+    }
   }
 
 
   JobOperationValue* OrthancJobUnserializer::UnserializeValue(const Json::Value& source)
   {
-    const std::string type = GetString(source, "Type");
+    const std::string type = ReadString(source, "Type");
 
     if (type == "DicomInstance")
     {
-      return new DicomInstanceOperationValue(context_, GetString(source, "ID"));
+      return new DicomInstanceOperationValue(context_, ReadString(source, "ID"));
     }
     else
     {
--- a/UnitTestsSources/MultiThreadingTests.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -36,9 +36,6 @@
 
 #include "../Core/FileStorage/MemoryStorageArea.h"
 #include "../Core/JobsEngine/JobsEngine.h"
-#include "../Core/JobsEngine/Operations/LogJobOperation.h"
-#include "../Core/JobsEngine/Operations/NullOperationValue.h"
-#include "../Core/JobsEngine/Operations/StringOperationValue.h"
 #include "../Core/MultiThreading/SharedMessageQueue.h"
 #include "../Core/OrthancException.h"
 #include "../Core/SystemToolbox.h"
@@ -46,8 +43,21 @@
 #include "../OrthancServer/DatabaseWrapper.h"
 #include "../OrthancServer/ServerContext.h"
 #include "../OrthancServer/ServerJobs/LuaJobManager.h"
+#include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h"
+
+#include "../Core/JobsEngine/Operations/JobOperationValues.h"
+#include "../Core/JobsEngine/Operations/NullOperationValue.h"
+#include "../Core/JobsEngine/Operations/StringOperationValue.h"
 #include "../OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h"
-#include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h"
+
+#include "../Core/JobsEngine/Operations/LogJobOperation.h"
+#include "../OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h"
+#include "../OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h"
+#include "../OrthancServer/ServerJobs/Operations/StorePeerOperation.h"
+#include "../OrthancServer/ServerJobs/Operations/StoreScuOperation.h"
+#include "../OrthancServer/ServerJobs/Operations/SystemCallOperation.h"
+
+
 
 using namespace Orthanc;
 
@@ -159,7 +169,7 @@
   public:
     virtual IJob* UnserializeJob(const Json::Value& value)
     {
-      if (GetString(value, "Type") == "DummyInstancesJob")
+      if (ReadString(value, "Type") == "DummyInstancesJob")
       {
         return new DummyInstancesJob(value);
       }
@@ -725,10 +735,36 @@
 }
 
 
+TEST(JobsSerialization, BadFileFormat)
+{
+  GenericJobUnserializer unserializer;
+
+  Json::Value s;
+
+  s = Json::objectValue;
+  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+
+  s = Json::arrayValue;
+  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+
+  s = "hello";
+  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+
+  s = 42;
+  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+}
+
+
 TEST(JobsSerialization, GenericValues)
 {
-  GenericJobUnserializer unserializer;
-    
   Json::Value s;
 
   {
@@ -736,9 +772,11 @@
     null.Serialize(s);
   }
 
-  std::auto_ptr<JobOperationValue> value;
+  GenericJobUnserializer unserializer;
   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+
+  std::auto_ptr<JobOperationValue> value;
   value.reset(unserializer.UnserializeValue(s));
   
   ASSERT_EQ(JobOperationValue::Type_Null, value->GetType());
@@ -757,10 +795,60 @@
 }
 
 
-TEST(JobsSerialization, GenericJobs)
+TEST(JobsSerialization, JobOperationValues)
 {
+  Json::Value s;
+
+  {
+    JobOperationValues values;
+    values.Append(new NullOperationValue);
+    values.Append(new StringOperationValue("hello"));
+    values.Append(new StringOperationValue("world"));
+    values.Serialize(s);
+  }
+
+  {
+    GenericJobUnserializer unserializer;
+    std::auto_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s));
+    ASSERT_EQ(3u, values->GetSize());
+    ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType());
+    ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType());
+    ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(2).GetType());
+
+    ASSERT_EQ("hello", dynamic_cast<const StringOperationValue&>(values->GetValue(1)).GetContent());
+    ASSERT_EQ("world", dynamic_cast<const StringOperationValue&>(values->GetValue(2)).GetContent());
+  }
+}
+
+
+TEST(JobsSerialization, GenericOperations)
+{   
+  Json::Value s;
+
+  {
+    LogJobOperation operation;
+    operation.Serialize(s);
+  }
+
   DummyUnserializer unserializer;
+  ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
+  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+
+  {
+    std::auto_ptr<IJobOperation> operation;
+    operation.reset(unserializer.UnserializeOperation(s));
     
+  }
+
+  {
+    
+  }
+}
+
+
+
+TEST(JobsSerialization, GenericJobs)
+{   
   Json::Value s;
 
   {
@@ -772,59 +860,108 @@
     job.Serialize(s);
   }
 
-  ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
-  ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
+  {
+    DummyUnserializer unserializer;
+    ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
+    ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
 
-  std::auto_ptr<IJob> job;
-  job.reset(unserializer.UnserializeJob(s));
-  ASSERT_EQ("description", dynamic_cast<DummyInstancesJob&>(*job).GetDescription());
-  //ASSERT_EQ("nope", dynamic_cast<DummyInstancesJob&>(*job).GetInstance(0));
+    std::auto_ptr<IJob> job;
+    job.reset(unserializer.UnserializeJob(s));
+    ASSERT_EQ("description", dynamic_cast<DummyInstancesJob&>(*job).GetDescription());
+    //ASSERT_EQ("nope", dynamic_cast<DummyInstancesJob&>(*job).GetInstance(0));
+  }
 }
 
 
-TEST(JobsSerialization, OrthancValues)
+namespace
 {
-  MemoryStorageArea storage;
-  DatabaseWrapper db;   // The SQLite DB is in memory
-  db.Open();
-  ServerContext context(db, storage);
+  class OrthancJobsSerialization : public testing::Test
+  {
+  private:
+    MemoryStorageArea              storage_;
+    DatabaseWrapper                db_;   // The SQLite DB is in memory
+    std::auto_ptr<ServerContext>   context_;
+    TimeoutDicomConnectionManager  manager_;
+
+  public:
+    OrthancJobsSerialization()
+    {
+      db_.Open();
+      context_.reset(new ServerContext(db_, storage_));
+    }
+
+    virtual ~OrthancJobsSerialization()
+    {
+      context_->Stop();
+      context_.reset(NULL);
+      db_.Close();
+    }
 
+    ServerContext& GetContext() 
+    {
+      return *context_;
+    }
+
+    bool CreateInstance(std::string& id)
+    {
+      // Create a sample DICOM file
+      ParsedDicomFile dicom(true);
+      dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"),
+                    false, DicomReplaceMode_InsertIfAbsent);
+
+      DicomInstanceToStore toStore;
+      toStore.SetParsedDicomFile(dicom);
+
+      return (context_->Store(id, toStore) == StoreStatus_Success);
+    }
+  };
+}
+
+
+TEST_F(OrthancJobsSerialization, Values)
+{
   std::string id;
-  
-  {
-    ParsedDicomFile dicom(true);
-    dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"),
-                  false, DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(CreateInstance(id));
 
-    DicomInstanceToStore toStore;
-    toStore.SetParsedDicomFile(dicom);
-    ASSERT_EQ(StoreStatus_Success, context.Store(id, toStore));
-  }
+  Json::Value s;
 
   {
-    OrthancJobUnserializer unserializer(context);
-    
-    Json::Value s;
-
-    {
-      DicomInstanceOperationValue instance(context, id);
-      instance.Serialize(s);
-    }
-
-    std::auto_ptr<JobOperationValue> value;
-    value.reset(unserializer.UnserializeValue(s));
-    ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType());
-    ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId());
-
-    {
-      std::string content;
-      dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content);
-
-      ParsedDicomFile dicom(content);
-      ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME));
-      ASSERT_EQ("JODOGNE", content);
-    }
+    DicomInstanceOperationValue instance(GetContext(), id);
+    instance.Serialize(s);
   }
 
-  context.Stop();
+  OrthancJobUnserializer unserializer(GetContext());
+    
+  std::auto_ptr<JobOperationValue> value;
+  value.reset(unserializer.UnserializeValue(s));
+  ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType());
+  ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId());
+
+  {
+    std::string content;
+    dynamic_cast<DicomInstanceOperationValue&>(*value).ReadDicom(content);
+
+    ParsedDicomFile dicom(content);
+    ASSERT_TRUE(dicom.GetTagValue(content, DICOM_TAG_PATIENT_NAME));
+    ASSERT_EQ("JODOGNE", content);
+  }
 }
+
+
+TEST_F(OrthancJobsSerialization, Operations)
+{
+  std::string id;
+  ASSERT_TRUE(CreateInstance(id));
+
+  Json::Value s;
+
+  {
+    DeleteResourceOperation operation(GetContext());
+    operation.Serialize(s);
+  }
+
+  OrthancJobUnserializer unserializer(GetContext());
+    
+  std::auto_ptr<IJobOperation> operation;
+  operation.reset(unserializer.UnserializeOperation(s));
+}
--- a/UnitTestsSources/SQLiteChromiumTests.cpp	Tue Jun 05 17:09:18 2018 +0200
+++ b/UnitTestsSources/SQLiteChromiumTests.cpp	Tue Jun 05 17:57:49 2018 +0200
@@ -51,35 +51,38 @@
  ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc
  ********************************************************************/
 
-class SQLConnectionTest : public testing::Test 
+namespace
 {
-public:
-  SQLConnectionTest()
-  {
-  }
-
-  virtual ~SQLConnectionTest()
-  {
-  }
-
-  virtual void SetUp() 
+  class SQLConnectionTest : public testing::Test 
   {
-    db_.OpenInMemory();
-  }
+  public:
+    SQLConnectionTest()
+    {
+    }
 
-  virtual void TearDown() 
-  {
-    db_.Close();
-  }
+    virtual ~SQLConnectionTest()
+    {
+    }
+
+    virtual void SetUp() 
+    {
+      db_.OpenInMemory();
+    }
 
-  Connection& db() 
-  { 
-    return db_; 
-  }
+    virtual void TearDown() 
+    {
+      db_.Close();
+    }
 
-private:
-  Connection db_;
-};
+    Connection& db() 
+    { 
+      return db_; 
+    }
+
+  private:
+    Connection db_;
+  };
+}
 
 
 
@@ -266,23 +269,26 @@
  ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc
  ********************************************************************/
 
-class SQLTransactionTest : public SQLConnectionTest
+namespace
 {
-public:
-  virtual void SetUp()
+  class SQLTransactionTest : public SQLConnectionTest
   {
-    SQLConnectionTest::SetUp();
-    ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  }
+  public:
+    virtual void SetUp()
+    {
+      SQLConnectionTest::SetUp();
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+    }
 
-  // Returns the number of rows in table "foo".
-  int CountFoo() 
-  {
-    Statement count(db(), "SELECT count(*) FROM foo");
-    count.Step();
-    return count.ColumnInt(0);
-  }
-};
+    // Returns the number of rows in table "foo".
+    int CountFoo() 
+    {
+      Statement count(db(), "SELECT count(*) FROM foo");
+      count.Step();
+      return count.ColumnInt(0);
+    }
+  };
+}
 
 
 TEST_F(SQLTransactionTest, Commit) {