changeset 5138:d00db9fb48fb

added OrthancPluginCreateJob2() in the plugin SDK
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 19 Jan 2023 19:04:13 +0100
parents 15109c3f0f7d
children b2b38f9fb9d1
files NEWS OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/PluginsJob.cpp OrthancServer/Plugins/Engine/PluginsJob.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h
diffstat 7 files changed, 386 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Jan 18 17:58:51 2023 +0100
+++ b/NEWS	Thu Jan 19 19:04:13 2023 +0100
@@ -33,10 +33,13 @@
 * Tolerance for "image/jpg" MIME type instead of "image/jpeg" in /tools/create-dicom
 * /system: added MaximumStorageMode and MaximumStorageSize
 
-Common plugins code (C++)
--------------------------
-
-* Added a 'header' argument to all OrthancPeers::DoPost, DoPut, ...
+Plugins
+-------
+
+* Added a "header" argument to all OrthancPeers::DoPost, DoPut, ... in the "OrthancPluginCppWrapper"
+* Added "OrthancPluginCreateJob2()" in the plugin SDK to avoid
+  possible crashes when "OrthancPluginJobGetContent()" or
+  "OrthancPluginJobGetSerialized()" get called
 
 Maintenance
 -----------
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Jan 19 19:04:13 2023 +0100
@@ -5111,6 +5111,14 @@
         return true;
       }
 
+      case _OrthancPluginService_CreateJob2:
+      {
+        const _OrthancPluginCreateJob2& p =
+          *reinterpret_cast<const _OrthancPluginCreateJob2*>(parameters);
+        *(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
+        return true;
+      }
+
       case _OrthancPluginService_FreeJob:
       {
         const _OrthancPluginFreeJob& p =
--- a/OrthancServer/Plugins/Engine/PluginsJob.cpp	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Engine/PluginsJob.cpp	Thu Jan 19 19:04:13 2023 +0100
@@ -36,8 +36,7 @@
 
 namespace Orthanc
 {
-  PluginsJob::PluginsJob(const _OrthancPluginCreateJob& parameters) :
-    parameters_(parameters)
+  void PluginsJob::Setup()
   {
     if (parameters_.job == NULL)
     {
@@ -48,17 +47,47 @@
         parameters_.finalize == NULL ||
         parameters_.type == NULL ||
         parameters_.getProgress == NULL ||
-        parameters_.getContent == NULL ||
-        parameters_.getSerialized == NULL ||
+        (parameters_.getContent == NULL && deprecatedGetContent_ == NULL) ||
+        (parameters_.getSerialized == NULL && deprecatedGetSerialized_ == NULL) ||
         parameters_.step == NULL ||
         parameters_.stop == NULL ||
         parameters_.reset == NULL)
     {
-      parameters_.finalize(parameters.job);
+      parameters_.finalize(parameters_.job);
       throw OrthancException(ErrorCode_NullPointer);
     }
 
-    type_.assign(parameters.type);
+    type_.assign(parameters_.type);
+  }
+  
+  PluginsJob::PluginsJob(const _OrthancPluginCreateJob2& parameters) :
+    parameters_(parameters),
+    deprecatedGetContent_(NULL),
+    deprecatedGetSerialized_(NULL)
+  {
+    Setup();
+  }
+
+  PluginsJob::PluginsJob(const _OrthancPluginCreateJob& parameters)
+  {
+    LOG(WARNING) << "Your plugin is using the deprecated OrthancPluginCreateJob() function";
+
+    memset(&parameters_, 0, sizeof(parameters_));
+    parameters_.target = parameters.target;
+    parameters_.job = parameters.job;
+    parameters_.finalize = parameters.finalize;
+    parameters_.type = parameters.type;
+    parameters_.getProgress = parameters.getProgress;
+    parameters_.getContent = NULL;
+    parameters_.getSerialized = NULL;
+    parameters_.step = parameters.step;
+    parameters_.stop = parameters.stop;
+    parameters_.reset = parameters.reset;
+
+    deprecatedGetContent_ = parameters.getContent;
+    deprecatedGetSerialized_ = parameters.getSerialized;
+    
+    Setup();
   }
 
   PluginsJob::~PluginsJob()
@@ -122,53 +151,148 @@
     return parameters_.getProgress(parameters_.job);
   }
 
+
+  namespace
+  {
+    class MemoryBufferRaii : public boost::noncopyable
+    {
+    private:
+      OrthancPluginMemoryBuffer  buffer_;
+
+    public:
+      MemoryBufferRaii()
+      {
+        buffer_.size = 0;
+        buffer_.data = NULL;
+      }
+
+      ~MemoryBufferRaii()
+      {
+        if (buffer_.size != 0)
+        {
+          free(buffer_.data);
+        }
+      }
+
+      OrthancPluginMemoryBuffer* GetObject()
+      {
+        return &buffer_;
+      }
+
+      void ToJsonObject(Json::Value& target) const
+      {
+        if ((buffer_.data == NULL && buffer_.size != 0) ||
+            (buffer_.data != NULL && buffer_.size == 0) ||
+            !Toolbox::ReadJson(target, buffer_.data, buffer_.size) ||
+            target.type() != Json::objectValue)
+        {
+          throw OrthancException(ErrorCode_Plugin,
+                                 "A job plugin must provide a JSON object as its public content and as its serialization");
+        }
+      }
+    };
+  }
+  
   void PluginsJob::GetPublicContent(Json::Value& value)
   {
-    const char* content = parameters_.getContent(parameters_.job);
+    if (parameters_.getContent != NULL)
+    {
+      MemoryBufferRaii target;
+
+      OrthancPluginErrorCode code = parameters_.getContent(target.GetObject(), parameters_.job);
 
-    if (content == NULL)
-    {
-      value = Json::objectValue;
+      if (code != OrthancPluginErrorCode_Success)
+      {
+        throw OrthancException(static_cast<ErrorCode>(code));
+      }
+      else
+      {
+        target.ToJsonObject(value);
+      }
     }
     else
     {
-      if (!Toolbox::ReadJson(value, content) ||
-          value.type() != Json::objectValue)
+      // This was the source code in Orthanc <= 1.11.2
+      const char* content = deprecatedGetContent_(parameters_.job);
+
+      if (content == NULL)
       {
-        throw OrthancException(ErrorCode_Plugin,
-                               "A job plugin must provide a JSON object as its public content");
+        value = Json::objectValue;
+      }
+      else
+      {
+        if (!Toolbox::ReadJson(value, content) ||
+            value.type() != Json::objectValue)
+        {
+          throw OrthancException(ErrorCode_Plugin,
+                                 "A job plugin must provide a JSON object as its public content");
+        }
       }
     }
   }
 
   bool PluginsJob::Serialize(Json::Value& value)
   {
-    const char* serialized = parameters_.getSerialized(parameters_.job);
+    if (parameters_.getSerialized != NULL)
+    {
+      MemoryBufferRaii target;
+
+      int32_t code = parameters_.getContent(target.GetObject(), parameters_.job);
 
-    if (serialized == NULL)
-    {
-      return false;
+      if (code < 0)
+      {
+        throw OrthancException(ErrorCode_Plugin, "Error during the serialization of a job");
+      }
+      else if (code == 0)
+      {
+        return false;  // Serialization is not implemented
+      }
+      else
+      {
+        target.ToJsonObject(value);
+
+        static const char* KEY_TYPE = "Type";
+      
+        if (value.isMember(KEY_TYPE))
+        {
+          throw OrthancException(ErrorCode_Plugin,
+                                 "The \"Type\" field is for reserved use for serialized job");
+        }
+
+        value[KEY_TYPE] = type_;
+        return true;
+      }
     }
     else
     {
-      if (!Toolbox::ReadJson(value, serialized) ||
-          value.type() != Json::objectValue)
+      // This was the source code in Orthanc <= 1.11.2
+      const char* serialized = deprecatedGetSerialized_(parameters_.job);
+
+      if (serialized == NULL)
       {
-        throw OrthancException(ErrorCode_Plugin,
-                               "A job plugin must provide a JSON object as its serialized content");
+        return false;
       }
+      else
+      {
+        if (!Toolbox::ReadJson(value, serialized) ||
+            value.type() != Json::objectValue)
+        {
+          throw OrthancException(ErrorCode_Plugin,
+                                 "A job plugin must provide a JSON object as its serialized content");
+        }
 
 
-      static const char* KEY_TYPE = "Type";
+        static const char* KEY_TYPE = "Type";
       
-      if (value.isMember(KEY_TYPE))
-      {
-        throw OrthancException(ErrorCode_Plugin,
-                               "The \"Type\" field is for reserved use for serialized job");
+        if (value.isMember(KEY_TYPE))
+        {
+          throw OrthancException(ErrorCode_Plugin,
+                                 "The \"Type\" field is for reserved use for serialized job");
+        }
+
+        value[KEY_TYPE] = type_;
+        return true;
       }
-
-      value[KEY_TYPE] = type_;
-      return true;
     }
   }
 }
--- a/OrthancServer/Plugins/Engine/PluginsJob.h	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Engine/PluginsJob.h	Thu Jan 19 19:04:13 2023 +0100
@@ -33,12 +33,18 @@
   class PluginsJob : public IJob
   {
   private:
-    _OrthancPluginCreateJob  parameters_;
-    std::string              type_;
+    _OrthancPluginCreateJob2       parameters_;
+    std::string                    type_;
+    OrthancPluginJobGetContent     deprecatedGetContent_;
+    OrthancPluginJobGetSerialized  deprecatedGetSerialized_;
+
+    void Setup();
 
   public:
     explicit PluginsJob(const _OrthancPluginCreateJob& parameters);
 
+    explicit PluginsJob(const _OrthancPluginCreateJob2& parameters);
+
     virtual ~PluginsJob();
 
     virtual void Start() ORTHANC_OVERRIDE
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Jan 19 19:04:13 2023 +0100
@@ -120,7 +120,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     11
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  2
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  3
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -578,10 +578,11 @@
     _OrthancPluginService_GetPeerUserProperty = 8007,
 
     /* Primitives for handling jobs (new in 1.4.2) */
-    _OrthancPluginService_CreateJob = 9000,
+    _OrthancPluginService_CreateJob = 9000,   /* Deprecated since SDK 1.11.3 */
     _OrthancPluginService_FreeJob = 9001,
     _OrthancPluginService_SubmitJob = 9002,
     _OrthancPluginService_RegisterJobsUnserializer = 9003,
+    _OrthancPluginService_CreateJob2 = 9004,  /* New in SDK 1.11.3 */
     
     _OrthancPluginService_INTERNAL = 0x7fffffff
   } _OrthancPluginService;
@@ -1577,11 +1578,31 @@
    * @param job The job of interest.
    * @return The statistics, as a JSON object encoded as a string.
    * @ingroup Toolbox
+   * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3.
    **/  
   typedef const char* (*OrthancPluginJobGetContent) (void* job);
 
 
   /**
+   * @brief Callback to retrieve the content of one custom job.
+   *
+   * Signature of a callback function that returns human-readable
+   * statistics about the job. This statistics must be formatted as a
+   * JSON object. This information is notably displayed in the "Jobs"
+   * tab of "Orthanc Explorer".
+   *
+   * @param target The target memory buffer where to store the JSON string.
+   * This buffer must be allocated using OrthancPluginCreateMemoryBuffer()
+   * and will be freed by the Orthanc core.
+   * @param job The job of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginJobGetContent2) (OrthancPluginMemoryBuffer* target,
+                                                                 void* job);
+
+
+  /**
    * @brief Callback to serialize one custom job.
    * 
    * Signature of a callback function that returns a serialized
@@ -1595,11 +1616,33 @@
    * @return The serialized job, as a JSON object encoded as a string.
    * @see OrthancPluginRegisterJobsUnserializer()
    * @ingroup Toolbox
+   * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3.
    **/  
   typedef const char* (*OrthancPluginJobGetSerialized) (void* job);
 
 
   /**
+   * @brief Callback to serialize one custom job.
+   *
+   * Signature of a callback function that returns a serialized
+   * version of the job, formatted as a JSON object. This
+   * serialization is stored in the Orthanc database, and is used to
+   * reload the job on the restart of Orthanc. The "unserialization"
+   * callback (with OrthancPluginJobsUnserializer signature) will
+   * receive this serialized object.
+   *
+   * @param target The target memory buffer where to store the JSON string.
+   * This buffer must be allocated using OrthancPluginCreateMemoryBuffer()
+   * and will be freed by the Orthanc core.
+   * @param job The job of interest.
+   * @return 1 if the serialization has succeeded, 0 if serialization is
+   * not implemented for this type of job, or -1 in case of error.
+   **/
+  typedef int32_t (*OrthancPluginJobGetSerialized2) (OrthancPluginMemoryBuffer* target,
+                                                     void* job);
+
+
+  /**
    * @brief Callback to execute one step of a custom job.
    * 
    * Signature of a callback function that executes one step in the
@@ -6692,6 +6735,7 @@
    * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(),
    * as long as it is not submitted with OrthancPluginSubmitJob().
    * @ingroup Toolbox
+   * @deprecated This signature should not be used anymore since Orthanc SDK 1.11.3.
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob(
     OrthancPluginContext           *context,
@@ -6736,6 +6780,92 @@
 
   typedef struct
   {
+    OrthancPluginJob**              target;
+    void                           *job;
+    OrthancPluginJobFinalize        finalize;
+    const char                     *type;
+    OrthancPluginJobGetProgress     getProgress;
+    OrthancPluginJobGetContent2     getContent;
+    OrthancPluginJobGetSerialized2  getSerialized;
+    OrthancPluginJobStep            step;
+    OrthancPluginJobStop            stop;
+    OrthancPluginJobReset           reset;
+  } _OrthancPluginCreateJob2;
+
+  /**
+   * @brief Create a custom job.
+   *
+   * This function creates a custom job to be run by the jobs engine
+   * of Orthanc.
+   * 
+   * Orthanc starts one dedicated thread per custom job that is
+   * running. It is guaranteed that all the callbacks will only be
+   * called from this single dedicated thread, in mutual exclusion: As
+   * a consequence, it is *not* mandatory to protect the various
+   * callbacks by mutexes.
+   * 
+   * The custom job can nonetheless launch its own processing threads
+   * on the first call to the "step()" callback, and stop them once
+   * the "stop()" callback is called.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param job The job to be executed.
+   * @param finalize The finalization callback.
+   * @param type The type of the job, provided to the job unserializer. 
+   * See OrthancPluginRegisterJobsUnserializer().
+   * @param getProgress The progress callback.
+   * @param getContent The content callback.
+   * @param getSerialized The serialization callback.
+   * @param step The callback to execute the individual steps of the job.
+   * @param stop The callback that is invoked once the job leaves the "running" state.
+   * @param reset The callback that is invoked if a stopped job is started again.
+   * @return The newly allocated job. It must be freed with OrthancPluginFreeJob(),
+   * as long as it is not submitted with OrthancPluginSubmitJob().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginJob *OrthancPluginCreateJob2(
+    OrthancPluginContext           *context,
+    void                           *job,
+    OrthancPluginJobFinalize        finalize,
+    const char                     *type,
+    OrthancPluginJobGetProgress     getProgress,
+    OrthancPluginJobGetContent2     getContent,
+    OrthancPluginJobGetSerialized2  getSerialized,
+    OrthancPluginJobStep            step,
+    OrthancPluginJobStop            stop,
+    OrthancPluginJobReset           reset)
+  {
+    OrthancPluginJob* target = NULL;
+
+    _OrthancPluginCreateJob2 params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = &target;
+    params.job = job;
+    params.finalize = finalize;
+    params.type = type;
+    params.getProgress = getProgress;
+    params.getContent = getContent;
+    params.getSerialized = getSerialized;
+    params.step = step;
+    params.stop = stop;
+    params.reset = reset;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateJob2, &params) != OrthancPluginErrorCode_Success ||
+        target == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
     OrthancPluginJob*   job;
   } _OrthancPluginFreeJob;
 
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Jan 19 19:04:13 2023 +0100
@@ -2172,6 +2172,36 @@
   }
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  static OrthancPluginErrorCode CopyStringToMemoryBuffer(OrthancPluginMemoryBuffer* target,
+                                                         const std::string& source)
+  {
+    if (OrthancPluginCreateMemoryBuffer(globalContext_, target, source.size()) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginErrorCode_NotEnoughMemory;
+    }
+    else
+    {
+      if (!source.empty())
+      {
+        memcpy(target->data, source.c_str(), source.size());
+      }
+      
+      return OrthancPluginErrorCode_Success;
+    }
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  OrthancPluginErrorCode OrthancJob::CallbackGetContent(OrthancPluginMemoryBuffer* target,
+                                                        void* job)
+  {
+    assert(job != NULL);
+    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+    return CopyStringToMemoryBuffer(target, that.content_);
+  }
+#else
   const char* OrthancJob::CallbackGetContent(void* job)
   {
     assert(job != NULL);
@@ -2185,8 +2215,33 @@
       return 0;
     }
   }
-
-
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+  int32_t OrthancJob::CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
+                                            void* job)
+  {
+    assert(job != NULL);
+    OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+    
+    if (that.hasSerialized_)
+    {
+      if (CopyStringToMemoryBuffer(target, that.serialized_) == OrthancPluginErrorCode_Success)
+      {
+        return 1;
+      }
+      else
+      {
+        return -1;
+      }
+    }
+    else
+    {
+      return 0;
+    }
+  }
+#else
   const char* OrthancJob::CallbackGetSerialized(void* job)
   {
     assert(job != NULL);
@@ -2209,6 +2264,7 @@
       return 0;
     }
   }
+#endif
 
 
   OrthancPluginJobStepStatus OrthancJob::CallbackStep(void* job)
@@ -2340,10 +2396,15 @@
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_NullPointer);
     }
 
-    OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-      CallbackStep, CallbackStop, CallbackReset);
+    OrthancPluginJob* orthanc =
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+      OrthancPluginCreateJob2
+#else
+      OrthancPluginCreateJob
+#endif
+      (GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+       CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+       CallbackStep, CallbackStop, CallbackReset);
 
     if (orthanc == NULL)
     {
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Jan 18 17:58:51 2023 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Jan 19 19:04:13 2023 +0100
@@ -833,9 +833,19 @@
 
     static float CallbackGetProgress(void* job);
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+    static OrthancPluginErrorCode CallbackGetContent(OrthancPluginMemoryBuffer* target,
+                                                     void* job);
+#else
     static const char* CallbackGetContent(void* job);
+#endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
+    static int32_t CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
+                                         void* job);
+#else
     static const char* CallbackGetSerialized(void* job);
+#endif
 
     static OrthancPluginJobStepStatus CallbackStep(void* job);