diff OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 2640:c691fcf66071 jobs

ResourceModificationJob
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 28 May 2018 16:30:17 +0200
parents 75a404e40323
children ccc470091ea6
line wrap: on
line diff
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 28 14:39:22 2018 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon May 28 16:30:17 2018 +0200
@@ -54,14 +54,39 @@
   }
 
 
+  static int GetPriority(const Json::Value& request)
+  {
+    static const char* PRIORITY = "Priority";
+    
+    if (request.isMember(PRIORITY))
+    {
+      if (request[PRIORITY].type() == Json::intValue)
+      {
+        return request[PRIORITY].asInt();
+      }
+      else
+      {
+        LOG(ERROR) << "Field \"" << PRIORITY << "\" of a modification request should be an integer";
+        throw OrthancException(ErrorCode_BadFileFormat);        
+      }
+    }
+    else
+    {
+      return 0;   // Default priority
+    }
+  }
+
+
   static void ParseModifyRequest(DicomModification& target,
+                                 int& priority,
                                  const RestApiPostCall& call)
   {
-    // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
+    // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}'
 
     Json::Value request;
     if (call.ParseJsonRequest(request))
     {
+      priority = GetPriority(request);
       target.ParseModifyRequest(request);
     }
     else
@@ -72,6 +97,7 @@
 
 
   static void ParseAnonymizationRequest(DicomModification& target,
+                                        int& priority,
                                         RestApiPostCall& call)
   {
     // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm
@@ -80,6 +106,8 @@
     if (call.ParseJsonRequest(request) &&
         request.isObject())
     {
+      priority = GetPriority(request);
+
       bool patientNameReplaced;
       target.ParseAnonymizationRequest(patientNameReplaced, request);
 
@@ -110,53 +138,132 @@
   }
 
 
-  static void AnonymizeOrModifyResource(DicomModification* modificationRaw,  // Takes ownership
-                                        MetadataType metadataType,
-                                        ResourceType resourceType,
-                                        RestApiPostCall& call)
+
+  class ResourceModificationJob : public SetOfInstancesJob
   {
-    if (modificationRaw == NULL)
+  public:
+    class Output : public boost::noncopyable
     {
-      throw OrthancException(ErrorCode_NullPointer);
-    }
-    
-    std::auto_ptr<DicomModification> modification(modificationRaw);
-    
-    bool isFirst = true;
-    Json::Value result(Json::objectValue);
+    private:
+      boost::mutex  mutex_;
+      ResourceType  level_;
+      bool          isFirst_;
+      std::string   id_;
+      std::string   patientId_;
+
+    public:
+      Output(ResourceType  level) :
+        level_(level),
+        isFirst_(true)
+      {
+        if (level_ != ResourceType_Patient &&
+            level_ != ResourceType_Study &&
+            level_ != ResourceType_Series)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }            
+      }
 
-    ServerContext& context = OrthancRestApi::GetContext(call);
+      ResourceType GetLevel() const
+      {
+        return level_;
+      }
+
+      void Update(DicomInstanceHasher& hasher)
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        
+        if (isFirst_)
+        {
+          switch (level_)
+          {
+            case ResourceType_Series:
+              id_ = hasher.HashSeries();
+              break;
+
+            case ResourceType_Study:
+              id_ = hasher.HashStudy();
+              break;
+
+            case ResourceType_Patient:
+              id_ = hasher.HashPatient();
+              break;
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
 
-    typedef std::list<std::string> Instances;
-    Instances instances;
-    std::string id = call.GetUriComponent("id", "");
-    context.GetIndex().GetChildInstances(instances, id);
+          patientId_ = hasher.HashPatient();
+          isFirst_ = false;
+        }
+      }
 
-    if (instances.empty())
-    {
-      return;
-    }
+      bool Format(Json::Value& target)
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        
+        if (isFirst_)
+        {
+          return false;
+        }
+        else
+        {
+          target = Json::objectValue;
+          target["Type"] = EnumerationToString(level_);
+          target["ID"] = id_;
+          target["Path"] = GetBasePath(level_, id_);
+          target["PatientID"] = patientId_;
+          return true;
+        }
+      }
 
-
-    /**
-     * Loop over all the instances of the resource.
-     **/
+      bool GetIdentifier(std::string& id)
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        
+        if (isFirst_)
+        {
+          return false;
+        }
+        else
+        {
+          id = id_;
+          return true;
+        }
+      }
+    };
+    
+  private:
+    ServerContext&                    context_;
+    std::auto_ptr<DicomModification>  modification_;
+    boost::shared_ptr<Output>         output_;
+    bool                              isAnonymization_;
+    MetadataType                      metadataType_;
+    std::string                       description_;
+    DicomInstanceOrigin               origin_;
 
-    for (Instances::const_iterator it = instances.begin(); 
-         it != instances.end(); ++it)
+  protected:
+    bool HandleInstance(const std::string& instance)
     {
-      LOG(INFO) << "Modifying instance " << *it;
+      if (modification_.get() == NULL)
+      {
+        LOG(ERROR) << "No modification was provided for this job";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+
+      
+      LOG(INFO) << "Modifying instance in a job: " << instance;
 
       std::auto_ptr<ServerContext::DicomCacheLocker> locker;
 
       try
       {
-        locker.reset(new ServerContext::DicomCacheLocker(OrthancRestApi::GetContext(call), *it));
+        locker.reset(new ServerContext::DicomCacheLocker(context_, instance));
       }
       catch (OrthancException&)
       {
-        // This child instance has been removed in between
-        continue;
+        LOG(WARNING) << "An instance was removed after the job was issued: " << instance;
+        return false;
       }
 
 
@@ -169,10 +276,10 @@
        **/
 
       std::auto_ptr<ParsedDicomFile> modified(original.Clone(true));
-      modification->Apply(*modified);
+      modification_->Apply(*modified);
 
       DicomInstanceToStore toStore;
-      toStore.SetRestOrigin(call);
+      toStore.SetOrigin(origin_);
       toStore.SetParsedDicomFile(*modified);
 
 
@@ -182,6 +289,10 @@
        **/
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
+      
+      MetadataType metadataType = (isAnonymization_ ?
+                                   MetadataType_AnonymizedFrom :
+                                   MetadataType_ModifiedFrom);
 
       if (originalHasher.HashSeries() != modifiedHasher.HashSeries())
       {
@@ -198,8 +309,8 @@
         toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient());
       }
 
-      assert(*it == originalHasher.HashInstance());
-      toStore.AddMetadata(ResourceType_Instance, metadataType, *it);
+      assert(instance == originalHasher.HashInstance());
+      toStore.AddMetadata(ResourceType_Instance, metadataType, instance);
 
 
       /**
@@ -207,9 +318,9 @@
        **/
 
       std::string modifiedInstance;
-      if (context.Store(modifiedInstance, toStore) != StoreStatus_Success)
+      if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
       {
-        LOG(ERROR) << "Error while storing a modified instance " << *it;
+        LOG(ERROR) << "Error while storing a modified instance " << instance;
         throw OrthancException(ErrorCode_CannotStoreInstance);
       }
 
@@ -217,41 +328,127 @@
       assert(modifiedInstance == modifiedHasher.HashInstance());
 
 
-      /**
-       * Compute the JSON object that is returned by the REST call.
-       **/
-
-      if (isFirst)
+      if (output_.get() != NULL)
       {
-        std::string newId;
+        output_->Update(modifiedHasher);
+      }
 
-        switch (resourceType)
-        {
-          case ResourceType_Series:
-            newId = modifiedHasher.HashSeries();
-            break;
+      return true;
+    }
+    
+  public:
+    ResourceModificationJob(ServerContext& context) :
+      context_(context),
+      isAnonymization_(false)
+    {
+    }
 
-          case ResourceType_Study:
-            newId = modifiedHasher.HashStudy();
-            break;
-
-          case ResourceType_Patient:
-            newId = modifiedHasher.HashPatient();
-            break;
+    void SetModification(DicomModification* modification,   // Takes ownership
+                         bool isAnonymization)
+    {
+      if (modification == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+      else if (IsStarted())
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        modification_.reset(modification);
+        isAnonymization_ = isAnonymization;
+      }
+    }
 
-          default:
-            throw OrthancException(ErrorCode_InternalError);
-        }
+    void SetOutput(boost::shared_ptr<Output>& output)
+    {
+      if (IsStarted())
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        output_ = output;
+      }
+    }
 
-        result["Type"] = EnumerationToString(resourceType);
-        result["ID"] = newId;
-        result["Path"] = GetBasePath(resourceType, newId);
-        result["PatientID"] = modifiedHasher.HashPatient();
-        isFirst = false;
+    void SetOrigin(const DicomInstanceOrigin& origin)
+    {
+      if (IsStarted())
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        origin_ = origin;
       }
     }
 
-    call.GetOutput().AnswerJson(result);
+    void SetOrigin(const RestApiCall& call)
+    {
+      DicomInstanceOrigin tmp;
+      tmp.SetRestOrigin(call);      
+      SetOrigin(tmp);
+    }
+
+    virtual void ReleaseResources()
+    {
+    }
+
+    virtual void GetJobType(std::string& target)
+    {
+      target = "ResourceModification";
+    }
+
+    virtual void GetPublicContent(Json::Value& value)
+    {
+      SetOfInstancesJob::GetPublicContent(value);
+
+      value["IsAnonymization"] = isAnonymization_;
+    }
+    
+    virtual void GetInternalContent(Json::Value& value)
+    {
+      SetOfInstancesJob::GetInternalContent(value);
+
+      Json::Value tmp;
+      modification_->Serialize(tmp);
+      value["Modification"] = tmp;
+    }
+  };
+  
+
+
+  static void SubmitJob(std::auto_ptr<DicomModification>& modification,
+                        bool isAnonymization,
+                        ResourceType level,
+                        int priority,
+                        RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    std::auto_ptr<ResourceModificationJob> job(new ResourceModificationJob(context));
+    
+    boost::shared_ptr<ResourceModificationJob::Output> output(new ResourceModificationJob::Output(level));
+    job->SetModification(modification.release(), isAnonymization);
+    job->SetOutput(output);
+    job->SetOrigin(call);
+    job->SetDescription("REST API");
+
+    context.AddChildInstances(*job, call.GetUriComponent("id", ""));
+    
+    if (context.GetJobsEngine().GetRegistry().SubmitAndWait(job.release(), priority))
+    {
+      Json::Value json;
+      if (output->Format(json))
+      {
+        call.GetOutput().AnswerJson(json);
+        return;
+      }
+    }
+
+    call.GetOutput().SignalError(HttpStatus_500_InternalServerError);
   }
 
 
@@ -261,7 +458,8 @@
     DicomModification modification;
     modification.SetAllowManualIdentifiers(true);
 
-    ParseModifyRequest(modification, call);
+    int priority;
+    ParseModifyRequest(modification, priority, call);
 
     if (modification.IsReplaced(DICOM_TAG_PATIENT_ID))
     {
@@ -289,7 +487,8 @@
     DicomModification modification;
     modification.SetAllowManualIdentifiers(true);
 
-    ParseAnonymizationRequest(modification, call);
+    int priority;
+    ParseAnonymizationRequest(modification, priority, call);
 
     AnonymizeOrModifyInstance(modification, call);
   }
@@ -299,10 +498,12 @@
   static void ModifyResource(RestApiPostCall& call)
   {
     std::auto_ptr<DicomModification> modification(new DicomModification);
-    ParseModifyRequest(*modification, call);
+
+    int priority;
+    ParseModifyRequest(*modification, priority, call);
 
     modification->SetLevel(resourceType);
-    AnonymizeOrModifyResource(modification.release(), MetadataType_ModifiedFrom, resourceType, call);
+    SubmitJob(modification, false, resourceType, priority, call);
   }
 
 
@@ -310,9 +511,11 @@
   static void AnonymizeResource(RestApiPostCall& call)
   {
     std::auto_ptr<DicomModification> modification(new DicomModification);
-    ParseAnonymizationRequest(*modification, call);
 
-    AnonymizeOrModifyResource(modification.release(), MetadataType_AnonymizedFrom, resourceType, call);
+    int priority;
+    ParseAnonymizationRequest(*modification, priority, call);
+
+    SubmitJob(modification, true, resourceType, priority, call);
   }
 
 
@@ -321,7 +524,7 @@
                                    ParsedDicomFile& dicom)
   {
     DicomInstanceToStore toStore;
-    toStore.SetRestOrigin(call);
+    toStore.GetOrigin().SetRestOrigin(call);
     toStore.SetParsedDicomFile(dicom);
 
     ServerContext& context = OrthancRestApi::GetContext(call);