changeset 435:28ba73274919

registration of user-defined metadata
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 15 May 2013 15:57:05 +0200
parents ccf3a0a43dac
children d51186bf7602
files Core/EnumerationDictionary.h OrthancServer/OrthancInitialization.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h Resources/Configuration.json UnitTests/main.cpp
diffstat 6 files changed, 223 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/Core/EnumerationDictionary.h	Wed May 15 14:54:58 2013 +0200
+++ b/Core/EnumerationDictionary.h	Wed May 15 15:57:05 2013 +0200
@@ -55,6 +55,24 @@
     public:
       void Add(Enumeration value, const std::string& str)
       {
+        // Check if these values are free
+        if (enumerationToString_.find(value) != enumerationToString_.end() ||
+            stringToEnumeration_.find(str) != stringToEnumeration_.end())
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+
+        // Prevent the registration of a number
+        try
+        {
+          boost::lexical_cast<int>(str);
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+        catch (boost::bad_lexical_cast)
+        {
+          // OK, the string is not a number
+        }
+
         enumerationToString_[value] = str;
         stringToEnumeration_[str] = value;
         stringToEnumeration_[boost::lexical_cast<std::string>(static_cast<int>(value))] = value;
@@ -62,6 +80,15 @@
 
       Enumeration Translate(const std::string& str) const
       {
+        try
+        {
+          int value = boost::lexical_cast<int>(str);
+          return static_cast<Enumeration>(value);
+        }
+        catch (boost::bad_lexical_cast)
+        {
+        }
+
         typename StringToEnumeration::const_iterator
           found = stringToEnumeration_.find(str);
 
@@ -82,7 +109,7 @@
 
         if (found == enumerationToString_.end())
         {
-          throw OrthancException(ErrorCode_InexistentItem);
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
         }
         else
         {
--- a/OrthancServer/OrthancInitialization.cpp	Wed May 15 14:54:58 2013 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed May 15 15:57:05 2013 +0200
@@ -34,6 +34,7 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
+#include "ServerEnumerations.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/filesystem.hpp>
@@ -118,12 +119,51 @@
   }
 
 
+  static void RegisterUserMetadata()
+  {
+    if (configuration_->isMember("UserMetadata"))
+    {
+      const Json::Value& parameter = (*configuration_) ["UserMetadata"];
+
+      Json::Value::Members members = parameter.getMemberNames();
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
+        LOG(INFO) << "Registering user-specific metadata: " << info;
+
+        if (!parameter[members[i]].asBool())
+        {
+          LOG(ERROR) << "Not a number in this user-specific metadata: " << info;
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        int metadata = parameter[members[i]].asInt();
+
+        try
+        {
+          RegisterUserMetadata(metadata, members[i]);
+        }
+        catch (OrthancException e)
+        {
+          LOG(ERROR) << "Cannot register this user-specific metadata: " << info;
+          throw e;
+        }
+      }
+    }
+  }
+
+
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
+
+    InitializeServerEnumerations();
     defaultDirectory_ = boost::filesystem::current_path();
     ReadGlobalConfiguration(configurationFile);
+
     curl_global_init(CURL_GLOBAL_ALL);
+
+    RegisterUserMetadata();
   }
 
 
--- a/OrthancServer/ServerEnumerations.cpp	Wed May 15 14:54:58 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Wed May 15 15:57:05 2013 +0200
@@ -32,9 +32,54 @@
 #include "ServerEnumerations.h"
 
 #include "../Core/OrthancException.h"
+#include "../Core/EnumerationDictionary.h"
+
+#include <boost/thread.hpp>
 
 namespace Orthanc
 {
+  static boost::mutex enumerationsMutex_;
+  static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
+
+  void InitializeServerEnumerations()
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries");
+    dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
+    dictMetadataType_.Add(MetadataType_Instance_RemoteAet, "RemoteAET");
+    dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances");
+    dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom");
+    dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom");
+    dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate");
+  }
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string name)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+
+    if (metadata < static_cast<int>(MetadataType_StartUser) ||
+        metadata > static_cast<int>(MetadataType_EndUser))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictMetadataType_.Add(static_cast<MetadataType>(metadata), name);
+  }
+
+  const char* EnumerationToString(MetadataType type)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(type).c_str();
+  }
+
+  MetadataType StringToMetadata(const std::string& str)
+  {
+    boost::mutex::scoped_lock lock(enumerationsMutex_);
+    return dictMetadataType_.Translate(str);
+  }
+
   const char* EnumerationToString(ResourceType type)
   {
     switch (type)
--- a/OrthancServer/ServerEnumerations.h	Wed May 15 14:54:58 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed May 15 15:57:05 2013 +0200
@@ -81,7 +81,11 @@
     MetadataType_Series_ExpectedNumberOfInstances = 4,
     MetadataType_ModifiedFrom = 5,
     MetadataType_AnonymizedFrom = 6,
-    MetadataType_LastUpdate = 7
+    MetadataType_LastUpdate = 7,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    MetadataType_StartUser = 1024,
+    MetadataType_EndUser = 65535
   };
 
   enum ChangeType
@@ -99,11 +103,20 @@
     ChangeType_ModifiedPatient = 11
   };
 
+  void InitializeServerEnumerations();
+
+  void RegisterUserMetadata(int metadata,
+                            const std::string name);
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId);
 
+  MetadataType StringToMetadata(const std::string& str);
+
   const char* EnumerationToString(ResourceType type);
 
+  const char* EnumerationToString(MetadataType type);
+
   const char* EnumerationToString(SeriesStatus status);
 
   const char* EnumerationToString(StoreStatus status);
--- a/Resources/Configuration.json	Wed May 15 14:54:58 2013 +0200
+++ b/Resources/Configuration.json	Wed May 15 15:57:05 2013 +0200
@@ -1,104 +1,110 @@
 {
-    /**
-     * General configuration of Orthanc
-     **/
+  /**
+   * General configuration of Orthanc
+   **/
+
+  // The logical name of this instance of Orthanc. This one is
+  // displayed in Orthanc Explorer and at the URI "/system".
+  "Name" : "MyOrthanc",
 
-    // The logical name of this instance of Orthanc. This one is
-    // displayed in Orthanc Explorer and at the URI "/system".
-    "Name" : "MyOrthanc",
+  // Path to the directory that holds the heavyweight files
+  // (i.e. the raw DICOM instances)
+  "StorageDirectory" : "OrthancStorage",
 
-    // Path to the directory that holds the heavyweight files
-    // (i.e. the raw DICOM instances)
-    "StorageDirectory" : "OrthancStorage",
+  // Path to the directory that holds the SQLite index (if unset,
+  // the value of StorageDirectory is used). This index could be
+  // stored on a RAM-drive or a SSD device for performance reasons.
+  "IndexDirectory" : "OrthancStorage",
 
-    // Path to the directory that holds the SQLite index (if unset,
-    // the value of StorageDirectory is used). This index could be
-    // stored on a RAM-drive or a SSD device for performance reasons.
-    "IndexDirectory" : "OrthancStorage",
+  // Enable the transparent compression of the DICOM instances
+  "StorageCompression" : false,
 
-    // Enable the transparent compression of the DICOM instances
-    "StorageCompression" : false,
+  // Maximum size of the storage in MB (a value of "0" indicates no
+  // limit on the storage size)
+  "MaximumStorageSize" : 0,
 
-    // Maximum size of the storage in MB (a value of "0" indicates no
-    // limit on the storage size)
-    "MaximumStorageSize" : 0,
+  // Maximum number of patients that can be stored at a given time
+  // in the storage (a value of "0" indicates no limit on the number
+  // of patients)
+  "MaximumPatientCount" : 0,
+  
+  // List of paths to the custom Lua scripts to load into this
+  // instance of Orthanc
+  "LuaScripts" : [
+  ],
 
-    // Maximum number of patients that can be stored at a given time
-    // in the storage (a value of "0" indicates no limit on the number
-    // of patients)
-    "MaximumPatientCount" : 0,
-  
-    // List of paths to the custom Lua scripts to load into this
-    // instance of Orthanc
-    "LuaScripts" : [
-    ],
+  // Dictionary of the user-specific metadata. Each entry must map a
+  // number between 1024 and 65535 to an unique string.
+  "UserMetadata" : {
+    // "Sample" : 1024
+  },
 
 
 
-    /**
-     * Configuration of the HTTP server
-     **/
+  /**
+   * Configuration of the HTTP server
+   **/
 
-    // HTTP port for the REST services and for the GUI
-    "HttpPort" : 8042,
+  // HTTP port for the REST services and for the GUI
+  "HttpPort" : 8042,
 
 
 
-    /**
-     * Configuration of the DICOM server
-     **/
+  /**
+   * Configuration of the DICOM server
+   **/
 
-    // The DICOM Application Entity Title
-    "DicomAet" : "ORTHANC",
+  // The DICOM Application Entity Title
+  "DicomAet" : "ORTHANC",
 
-    // Check whether the called AET corresponds during a DICOM request
-    "DicomCheckCalledAet" : false,
+  // Check whether the called AET corresponds during a DICOM request
+  "DicomCheckCalledAet" : false,
 
-    // The DICOM port
-    "DicomPort" : 4242,
+  // The DICOM port
+  "DicomPort" : 4242,
 
 
 
-    /**
-     * Security-related options for the HTTP server
-     **/
+  /**
+   * Security-related options for the HTTP server
+   **/
 
-    // Whether remote hosts can connect to the HTTP server
-    "RemoteAccessAllowed" : false,
+  // Whether remote hosts can connect to the HTTP server
+  "RemoteAccessAllowed" : false,
 
-    // Whether or not SSL is enabled
-    "SslEnabled" : false,
+  // Whether or not SSL is enabled
+  "SslEnabled" : false,
 
-    // Path to the SSL certificate (meaningful only if SSL is enabled)
-    "SslCertificate" : "certificate.pem",
+  // Path to the SSL certificate (meaningful only if SSL is enabled)
+  "SslCertificate" : "certificate.pem",
 
-    // Whether or not the password protection is enabled
-    "AuthenticationEnabled" : false,
+  // Whether or not the password protection is enabled
+  "AuthenticationEnabled" : false,
 
-    // The list of the registered users. Because Orthanc uses HTTP
-    // Basic Authentication, the passwords are stored as plain text.
-    "RegisteredUsers" : {
-        "alice" : "alicePassword"
-    },
+  // The list of the registered users. Because Orthanc uses HTTP
+  // Basic Authentication, the passwords are stored as plain text.
+  "RegisteredUsers" : {
+    // "alice" : "alicePassword"
+  },
 
 
 
-    /**
-     * Network topology
-     **/
+  /**
+   * Network topology
+   **/
 
-    // The list of the known DICOM modalities
-    "DicomModalities" : {
-      /**
-       * Uncommenting the following line would enable Orthanc to
-       * connect to an instance of the "storescp" open-source DICOM
-       * store (shipped in the DCMTK distribution) started by the
-       * command line "storescp 2000".
-       **/
-      // "sample" : [ "STORESCP", "localhost", 2000 ]
-    },
+  // The list of the known DICOM modalities
+  "DicomModalities" : {
+    /**
+     * Uncommenting the following line would enable Orthanc to
+     * connect to an instance of the "storescp" open-source DICOM
+     * store (shipped in the DCMTK distribution) started by the
+     * command line "storescp 2000".
+     **/
+    // "sample" : [ "STORESCP", "localhost", 2000 ]
+  },
 
-    // The list of the known Orthanc peers (currently unused)
-    "OrthancPeers" : {
-    }
+  // The list of the known Orthanc peers (currently unused)
+  "OrthancPeers" : {
+  }
 }
--- a/UnitTests/main.cpp	Wed May 15 14:54:58 2013 +0200
+++ b/UnitTests/main.cpp	Wed May 15 15:57:05 2013 +0200
@@ -353,14 +353,19 @@
 {
   Toolbox::EnumerationDictionary<MetadataType>  d;
 
-  ASSERT_THROW(d.Translate("2"), OrthancException);
   ASSERT_THROW(d.Translate("ReceptionDate"), OrthancException);
+  ASSERT_EQ(MetadataType_ModifiedFrom, d.Translate("5"));
 
   d.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
 
   ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("ReceptionDate"));
   ASSERT_EQ(MetadataType_Instance_ReceptionDate, d.Translate("2"));
   ASSERT_EQ("ReceptionDate", d.Translate(MetadataType_Instance_ReceptionDate));
+
+  ASSERT_THROW(d.Add(MetadataType_Instance_ReceptionDate, "Hello"), OrthancException);
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "ReceptionDate"), OrthancException); // already used
+  ASSERT_THROW(d.Add(MetadataType_ModifiedFrom, "1024"), OrthancException); // cannot register numbers
+  d.Add(MetadataType_ModifiedFrom, "ModifiedFrom");  // ok
 }
 
 
@@ -377,6 +382,17 @@
   ASSERT_STREQ("Success", EnumerationToString(StoreStatus_Success));
 
   ASSERT_STREQ("CompletedSeries", EnumerationToString(ChangeType_CompletedSeries));
+
+  ASSERT_STREQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
+  ASSERT_STREQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
+
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
+  ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)
+  ASSERT_THROW(RegisterUserMetadata(128000, ""), OrthancException); // too high (> 65535)
+  RegisterUserMetadata(2047, "Ceci est un test");
+  ASSERT_EQ(2047, StringToMetadata("2047"));
+  ASSERT_EQ(2047, StringToMetadata("Ceci est un test"));
 }