changeset 3187:4bbadcd03966

refactoring retrieval of metadata from database
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 04 Feb 2019 12:06:19 +0100
parents 5d1f5984dc41
children 70356580e310
files CMakeLists.txt OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp OrthancServer/Database/Compatibility/ILookupResourceAndParent.h OrthancServer/Database/IDatabaseWrapper.h OrthancServer/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Database/SQLiteDatabaseWrapper.h OrthancServer/LuaScripting.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h Plugins/Engine/OrthancPluginDatabase.cpp Plugins/Engine/OrthancPluginDatabase.h UnitTestsSources/DicomMapTests.cpp UnitTestsSources/ServerIndexTests.cpp
diffstat 14 files changed, 491 insertions(+), 178 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Feb 01 09:28:12 2019 +0100
+++ b/CMakeLists.txt	Mon Feb 04 12:06:19 2019 +0100
@@ -56,6 +56,7 @@
   OrthancServer/Database/Compatibility/DatabaseLookup.cpp
   OrthancServer/Database/Compatibility/ICreateInstance.cpp
   OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp
+  OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp
   OrthancServer/Database/Compatibility/ILookupResources.cpp
   OrthancServer/Database/Compatibility/SetOfResources.cpp
   OrthancServer/Database/ResourcesContent.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../PrecompiledHeadersServer.h"
+#include "ILookupResourceAndParent.h"
+
+#include "../../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    bool ILookupResourceAndParent::Apply(ILookupResourceAndParent& database,
+                                         int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId)
+    {
+      if (!database.LookupResource(id, type, publicId))
+      {
+        return false;
+      }
+      else if (type == ResourceType_Patient)
+      {
+        parentPublicId.clear();
+        return true;
+      }
+      else
+      {
+        int64_t parentId;
+        if (!database.LookupParent(parentId, id))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        parentPublicId = database.GetPublicId(parentId);
+        return true;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.h	Mon Feb 04 12:06:19 2019 +0100
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../ServerEnumerations.h"
+
+#include <boost/noncopyable.hpp>
+#include <list>
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class ILookupResourceAndParent : public boost::noncopyable
+    {
+    public:
+      virtual bool LookupResource(int64_t& id,
+                                  ResourceType& type,
+                                  const std::string& publicId) = 0;
+
+      virtual bool LookupParent(int64_t& parentId,
+                                int64_t resourceId) = 0;
+
+      virtual std::string GetPublicId(int64_t resourceId) = 0;
+
+      static bool Apply(ILookupResourceAndParent& database,
+                        int64_t& id,
+                        ResourceType& type,
+                        std::string& parentPublicId,
+                        const std::string& publicId);
+    };
+  }
+}
--- a/OrthancServer/Database/IDatabaseWrapper.h	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/Database/IDatabaseWrapper.h	Mon Feb 04 12:06:19 2019 +0100
@@ -153,9 +153,6 @@
 
     virtual bool IsProtectedPatient(int64_t internalId) = 0;
 
-    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id) = 0;
-
     virtual void ListAvailableAttachments(std::list<FileContentType>& target,
                                           int64_t id) = 0;
 
@@ -245,5 +242,15 @@
                                      MetadataType metadata) = 0;
 
     virtual int64_t GetLastChangeIndex() = 0;
+
+
+    /**
+     * Primitives introduced in Orthanc 1.5.4
+     **/
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId) = 0;
   };
 }
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -761,21 +761,6 @@
   }
 
 
-  void SQLiteDatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
-                                                    int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
-    }
-  }
-
-
   void SQLiteDatabaseWrapper::AddAttachment(int64_t id,
                                             const FileInfo& attachment)
   {
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.h	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.h	Mon Feb 04 12:06:19 2019 +0100
@@ -38,6 +38,7 @@
 #include "../../Core/SQLite/Connection.h"
 #include "Compatibility/ICreateInstance.h"
 #include "Compatibility/IGetChildrenMetadata.h"
+#include "Compatibility/ILookupResourceAndParent.h"
 #include "Compatibility/ISetResourcesContent.h"
 
 namespace Orthanc
@@ -56,6 +57,7 @@
     public IDatabaseWrapper,
     public Compatibility::ICreateInstance,
     public Compatibility::IGetChildrenMetadata,
+    public Compatibility::ILookupResourceAndParent,
     public Compatibility::ISetResourcesContent
   {
   private:
@@ -224,10 +226,6 @@
                                 MetadataType type)
       ORTHANC_OVERRIDE;
 
-    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id)
-      ORTHANC_OVERRIDE;
-
     virtual void AddAttachment(int64_t id,
                                const FileInfo& attachment)
       ORTHANC_OVERRIDE;
@@ -361,5 +359,14 @@
     virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE;
 
     virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE;
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId)
+      ORTHANC_OVERRIDE
+    {
+      return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
+    }
   };
 }
--- a/OrthancServer/LuaScripting.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/LuaScripting.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -164,23 +164,37 @@
         }
       }
       
-      Json::Value tags, metadata;
-      if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType()) &&
-          that.context_.GetIndex().GetMetadata(metadata, change_.GetPublicId()))
+      Json::Value tags;
+      
+      if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType()))
       {
-        LuaScripting::Lock lock(that);
+        std::map<MetadataType, std::string> metadata;
+        that.context_.GetIndex().GetAllMetadata(metadata, change_.GetPublicId());
+        
+        Json::Value formattedMetadata = Json::objectValue;
 
-        if (lock.GetLua().IsExistingFunction(name))
+        for (std::map<MetadataType, std::string>::const_iterator 
+               it = metadata.begin(); it != metadata.end(); ++it)
         {
-          that.InitializeJob();
+          std::string key = EnumerationToString(it->first);
+          formattedMetadata[key] = it->second;
+        }      
+
+        {
+          LuaScripting::Lock lock(that);
 
-          LuaFunctionCall call(lock.GetLua(), name);
-          call.PushString(change_.GetPublicId());
-          call.PushJson(tags["MainDicomTags"]);
-          call.PushJson(metadata);
-          call.Execute();
+          if (lock.GetLua().IsExistingFunction(name))
+          {
+            that.InitializeJob();
 
-          that.SubmitJob();
+            LuaFunctionCall call(lock.GetLua(), name);
+            call.PushString(change_.GetPublicId());
+            call.PushJson(tags["MainDicomTags"]);
+            call.PushJson(formattedMetadata);
+            call.Execute();
+
+            that.SubmitJob();
+          }
         }
       }
     }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -705,9 +705,9 @@
     CheckValidResourceType(call);
     
     std::string publicId = call.GetUriComponent("id", "");
-    std::list<MetadataType> metadata;
+    std::map<MetadataType, std::string> metadata;
 
-    OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId);
+    OrthancRestApi::GetIndex(call).GetAllMetadata(metadata, publicId);
 
     Json::Value result;
 
@@ -715,25 +715,21 @@
     {
       result = Json::objectValue;
       
-      for (std::list<MetadataType>::const_iterator 
+      for (std::map<MetadataType, std::string>::const_iterator 
              it = metadata.begin(); it != metadata.end(); ++it)
       {
-        std::string value;
-        if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, *it))
-        {
-          std::string key = EnumerationToString(*it);
-          result[key] = value;
-        }
+        std::string key = EnumerationToString(it->first);
+        result[key] = it->second;
       }      
     }
     else
     {
       result = Json::arrayValue;
       
-      for (std::list<MetadataType>::const_iterator 
+      for (std::map<MetadataType, std::string>::const_iterator 
              it = metadata.begin(); it != metadata.end(); ++it)
       {       
-        result.append(EnumerationToString(*it));
+        result.append(EnumerationToString(it->first));
       }
     }
 
--- a/OrthancServer/ServerIndex.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/ServerIndex.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -559,12 +559,30 @@
 
 
 
-  bool ServerIndex::GetMetadataAsInteger(int64_t& result,
-                                         int64_t id,
-                                         MetadataType type)
+  static bool LookupStringMetadata(std::string& result,
+                                   const std::map<MetadataType, std::string>& metadata,
+                                   MetadataType type)
+  {
+    std::map<MetadataType, std::string>::const_iterator found = metadata.find(type);
+
+    if (found == metadata.end())
+    {
+      return false;
+    }
+    else
+    {
+      result = found->second;
+      return true;
+    }
+  }
+
+
+  static bool LookupIntegerMetadata(int64_t& result,
+                                    const std::map<MetadataType, std::string>& metadata,
+                                    MetadataType type)
   {
     std::string s;
-    if (!db_.LookupMetadata(s, id, type))
+    if (!LookupStringMetadata(s, metadata, type))
     {
       return false;
     }
@@ -947,10 +965,14 @@
 
   
       // Check whether the series of this new instance is now completed
-      SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_);
-      if (seriesStatus == SeriesStatus_Complete)
+      int64_t expectedNumberOfInstances;
+      if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary))
       {
-        LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries);
+        SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_, expectedNumberOfInstances);
+        if (seriesStatus == SeriesStatus_Complete)
+        {
+          LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries);
+        }
       }
       
 
@@ -1037,21 +1059,6 @@
   }
 
 
-  SeriesStatus ServerIndex::GetSeriesStatus(int64_t id)
-  {
-    // Get the expected number of instances in this series (from the metadata)
-    int64_t expected;
-    if (!GetMetadataAsInteger(expected, id, MetadataType_Series_ExpectedNumberOfInstances))
-    {
-      return SeriesStatus_Unknown;
-    }
-    else
-    {
-      return GetSeriesStatus(id, expected);
-    }
-  }
-
-
   void ServerIndex::MainDicomTagsToJson(Json::Value& target,
                                         int64_t resourceId,
                                         ResourceType resourceType)
@@ -1090,22 +1097,27 @@
     // Lookup for the requested resource
     int64_t id;
     ResourceType type;
-    if (!db_.LookupResource(id, type, publicId) ||
+    std::string parent;
+    if (!db_.LookupResourceAndParent(id, type, parent, publicId) ||
         type != expectedType)
     {
       return false;
     }
 
-    // Find the parent resource (if it exists)
-    if (type != ResourceType_Patient)
+    // Set information about the parent resource (if it exists)
+    if (type == ResourceType_Patient)
     {
-      int64_t parentId;
-      if (!db_.LookupParent(parentId, id))
+      if (!parent.empty())
       {
-        throw OrthancException(ErrorCode_InternalError);
+        throw OrthancException(ErrorCode_DatabasePlugin);
       }
-
-      std::string parent = db_.GetPublicId(parentId);
+    }
+    else
+    {
+      if (parent.empty())
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
 
       switch (type)
       {
@@ -1159,6 +1171,10 @@
       }
     }
 
+    // Extract the metadata
+    std::map<MetadataType, std::string> metadata;
+    db_.GetAllMetadata(metadata, id);
+
     // Set the resource type
     switch (type)
     {
@@ -1173,16 +1189,17 @@
       case ResourceType_Series:
       {
         result["Type"] = "Series";
-        result["Status"] = EnumerationToString(GetSeriesStatus(id));
 
         int64_t i;
-        if (GetMetadataAsInteger(i, id, MetadataType_Series_ExpectedNumberOfInstances))
+        if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances))
         {
           result["ExpectedNumberOfInstances"] = static_cast<int>(i);
+          result["Status"] = EnumerationToString(GetSeriesStatus(id, i));
         }
         else
         {
           result["ExpectedNumberOfInstances"] = Json::nullValue;
+          result["Status"] = EnumerationToString(SeriesStatus_Unknown);
         }
 
         break;
@@ -1202,7 +1219,7 @@
         result["FileUuid"] = attachment.GetUuid();
 
         int64_t i;
-        if (GetMetadataAsInteger(i, id, MetadataType_Instance_IndexInSeries))
+        if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries))
         {
           result["IndexInSeries"] = static_cast<int>(i);
         }
@@ -1224,12 +1241,12 @@
 
     std::string tmp;
 
-    if (db_.LookupMetadata(tmp, id, MetadataType_AnonymizedFrom))
+    if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom))
     {
       result["AnonymizedFrom"] = tmp;
     }
 
-    if (db_.LookupMetadata(tmp, id, MetadataType_ModifiedFrom))
+    if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom))
     {
       result["ModifiedFrom"] = tmp;
     }
@@ -1240,7 +1257,7 @@
     {
       result["IsStable"] = !unstableResources_.Contains(id);
 
-      if (db_.LookupMetadata(tmp, id, MetadataType_LastUpdate))
+      if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate))
       {
         result["LastUpdate"] = tmp;
       }
@@ -1825,19 +1842,19 @@
   }
 
 
-  void ServerIndex::ListAvailableMetadata(std::list<MetadataType>& target,
-                                          const std::string& publicId)
+  void ServerIndex::GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                   const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
-    ResourceType rtype;
+    ResourceType type;
     int64_t id;
-    if (!db_.LookupResource(id, rtype, publicId))
+    if (!db_.LookupResource(id, type, publicId))
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
 
-    db_.ListAvailableMetadata(target, id);
+    return db_.GetAllMetadata(target, id);
   }
 
 
@@ -2214,41 +2231,6 @@
   }
 
 
-  bool ServerIndex::GetMetadata(Json::Value& target,
-                                const std::string& publicId)
-  {
-    boost::mutex::scoped_lock lock(mutex_);
-
-    target = Json::objectValue;
-
-    ResourceType type;
-    int64_t id;
-    if (!db_.LookupResource(id, type, publicId))
-    {
-      return false;
-    }
-
-    std::list<MetadataType> metadata;
-    db_.ListAvailableMetadata(metadata, id);
-
-    for (std::list<MetadataType>::const_iterator
-           it = metadata.begin(); it != metadata.end(); ++it)
-    {
-      std::string key = EnumerationToString(*it);
-
-      std::string value;
-      if (!db_.LookupMetadata(value, id, *it))
-      {
-        value.clear();
-      }
-
-      target[key] = value;
-    }
-
-    return true;
-  }
-
-
   void ServerIndex::SetGlobalProperty(GlobalProperty property,
                                       const std::string& value)
   {
--- a/OrthancServer/ServerIndex.h	Fri Feb 01 09:28:12 2019 +0100
+++ b/OrthancServer/ServerIndex.h	Mon Feb 04 12:06:19 2019 +0100
@@ -84,8 +84,6 @@
                              int64_t resourceId,
                              ResourceType resourceType);
 
-    SeriesStatus GetSeriesStatus(int64_t id);
-
     bool IsRecyclingNeeded(uint64_t instanceSize);
 
     void Recycle(uint64_t instanceSize,
@@ -97,10 +95,6 @@
                         Orthanc::ResourceType type,
                         const std::string& publicId);
 
-    bool GetMetadataAsInteger(int64_t& result,
-                              int64_t id,
-                              MetadataType type);
-
     void LogChange(int64_t internalId,
                    ChangeType changeType,
                    ResourceType resourceType,
@@ -211,16 +205,13 @@
     void DeleteMetadata(const std::string& publicId,
                         MetadataType type);
 
+    void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                        const std::string& publicId);
+
     bool LookupMetadata(std::string& target,
                         const std::string& publicId,
                         MetadataType type);
 
-    void ListAvailableMetadata(std::list<MetadataType>& target,
-                               const std::string& publicId);
-
-    bool GetMetadata(Json::Value& target,
-                     const std::string& publicId);
-
     void ListAvailableAttachments(std::list<FileContentType>& target,
                                   const std::string& publicId,
                                   ResourceType expectedType);
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -390,21 +390,34 @@
   void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target,
                                              int64_t id)
   {
-    std::list<MetadataType> metadata;
-    ListAvailableMetadata(metadata, id);
+    // TODO - Add primitive in SDK
 
     target.clear();
 
-    for (std::list<MetadataType>::const_iterator
-           it = metadata.begin(); it != metadata.end(); ++it)
+    ResetAnswers();
+    CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
+
+    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
+        type_ != _OrthancPluginDatabaseAnswerType_Int32)
     {
-      std::string value;
-      if (!LookupMetadata(value, id, *it))
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
+
+    target.clear();
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
+    {
+      for (std::list<int32_t>::const_iterator 
+             it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
       {
-        throw OrthancException(ErrorCode_DatabasePlugin);
+        MetadataType type = static_cast<MetadataType>(*it);
+
+        std::string value;
+        if (LookupMetadata(value, id, type))
+        {
+          target[type] = value;
+        }
       }
-
-      target[*it] = value;
     }
   }
 
@@ -624,31 +637,6 @@
   }
 
 
-  void OrthancPluginDatabase::ListAvailableMetadata(std::list<MetadataType>& target,
-                                                    int64_t id)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
-
-    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-        type_ != _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-
-    target.clear();
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      for (std::list<int32_t>::const_iterator 
-             it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
-      {
-        target.push_back(static_cast<MetadataType>(*it));
-      }
-    }
-  }
-
-
   void OrthancPluginDatabase::ListAvailableAttachments(std::list<FileContentType>& target,
                                                        int64_t id)
   {
@@ -1431,4 +1419,14 @@
       CheckSuccess(extensions_.tagMostRecentPatient(payload_, patient));
     }
   }
+
+
+  bool OrthancPluginDatabase::LookupResourceAndParent(int64_t& id,
+                                                      ResourceType& type,
+                                                      std::string& parentPublicId,
+                                                      const std::string& publicId)
+  {
+    // TODO - Add primitive in SDK
+    return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
+  }
 }
--- a/Plugins/Engine/OrthancPluginDatabase.h	Fri Feb 01 09:28:12 2019 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Mon Feb 04 12:06:19 2019 +0100
@@ -39,6 +39,7 @@
 #include "../../OrthancServer/Database/Compatibility/ICreateInstance.h"
 #include "../../OrthancServer/Database/Compatibility/IGetChildrenMetadata.h"
 #include "../../OrthancServer/Database/Compatibility/ILookupResources.h"
+#include "../../OrthancServer/Database/Compatibility/ILookupResourceAndParent.h"
 #include "../../OrthancServer/Database/Compatibility/ISetResourcesContent.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
@@ -50,6 +51,7 @@
     public Compatibility::ICreateInstance,
     public Compatibility::IGetChildrenMetadata,
     public Compatibility::ILookupResources,
+    public Compatibility::ILookupResourceAndParent,
     public Compatibility::ISetResourcesContent
   {
   private:
@@ -225,10 +227,6 @@
     virtual bool IsProtectedPatient(int64_t internalId) 
       ORTHANC_OVERRIDE;
 
-    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id) 
-      ORTHANC_OVERRIDE;
-
     virtual void ListAvailableAttachments(std::list<FileContentType>& target,
                                           int64_t id) 
       ORTHANC_OVERRIDE;
@@ -364,6 +362,12 @@
     virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE;
   
     virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE;
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId)
+      ORTHANC_OVERRIDE;
   };
 }
 
--- a/UnitTestsSources/DicomMapTests.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/UnitTestsSources/DicomMapTests.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -551,3 +551,196 @@
   ASSERT_EQ("F", b.GetValue(DICOM_TAG_SLICE_THICKNESS).GetContent());
   ASSERT_FALSE(b.HasOnlyMainDicomTags());
 }
+
+
+
+
+#if 0
+
+namespace Orthanc
+{
+  class DicomJsonVisitor : public ITagVisitor
+  {
+  private:
+    Json::Value  result_;
+    std::string  bulkUriRoot_;
+
+    static std::string FormatTag(const DicomTag& tag)
+    {
+      char buf[16];
+      sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement());
+      return std::string(buf);
+    }
+    
+    Json::Value& CreateNode(const std::vector<DicomTag>& parentTags,
+                            const std::vector<size_t>& parentIndexes,
+                            const DicomTag& tag)
+    {
+      assert(parentTags.size() == parentIndexes.size());      
+
+      Json::Value* node = &result_;
+
+      if (parentTags.size() != 0)
+      {
+        printf("ICI %s\n", FormatTag(parentTags[0]).c_str());
+      }
+
+      for (size_t i = 0; i < parentTags.size(); i++)
+      {
+        std::string t = FormatTag(parentTags[i]);
+
+        if (!node->isMember(t))
+        {
+          Json::Value item = Json::objectValue;
+          item["vr"] = "SQ";
+          item["Value"] = Json::arrayValue;
+          item["Value"].append(Json::objectValue);
+          (*node) [t] = item;
+
+          node = &(*node)[t]["Value"][0];
+          std::cout << result_.toStyledString();
+        }
+        else if ((*node) [t].type() != Json::objectValue ||
+                 !(*node) [t].isMember("vr") ||
+                 (*node) [t]["vr"].type() != Json::stringValue ||
+                 (*node) [t]["vr"].asString() != "SQ" ||
+                 !(*node) [t].isMember("Value") ||
+                 (*node) [t]["Value"].type() != Json::arrayValue)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        else
+        {
+          std::cout << result_.toStyledString();
+          printf("%d %d\n", (*node) [t]["Value"].size(), parentIndexes[i]);
+          
+          if ((*node) [t]["Value"].size() >= parentIndexes[i])
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
+          else
+          {
+            (*node) [t]["Value"].append(Json::objectValue);
+            node = &(*node) [t]["Value"][Json::ArrayIndex(parentIndexes[i])];
+          }
+        }
+      }
+
+      assert(node->type() == Json::objectValue);
+
+      std::string t = FormatTag(tag);
+      if (node->isMember(t))
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      else
+      {
+        (*node) [t] = Json::objectValue;
+        return (*node) [t];
+      }
+    }                              
+
+  public:
+    DicomJsonVisitor()
+    {
+      Clear();
+    }
+
+    void SetBulkUriRoot(const std::string& root)
+    {
+      bulkUriRoot_ = root;
+    }
+    
+    void Clear()
+    {
+      result_ = Json::objectValue;
+    }
+
+    const Json::Value& GetResult() const
+    {
+      return result_;
+    }
+
+    virtual void VisitUnknown(const std::vector<DicomTag>& parentTags,
+                              const std::vector<size_t>& parentIndexes,
+                              const DicomTag& tag,
+                              ValueRepresentation vr) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
+                             const std::vector<size_t>& parentIndexes,
+                             const DicomTag& tag,
+                             ValueRepresentation vr,
+                             const void* data,
+                             size_t size) ORTHANC_OVERRIDE
+    {
+      if (!bulkUriRoot_.empty())
+      {
+        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      }
+    }
+
+    virtual void VisitInteger(const std::vector<DicomTag>& parentTags,
+                              const std::vector<size_t>& parentIndexes,
+                              const DicomTag& tag,
+                              ValueRepresentation vr,
+                              int64_t value) ORTHANC_OVERRIDE
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      
+    }
+
+    virtual void VisitDouble(const std::vector<DicomTag>& parentTags,
+                             const std::vector<size_t>& parentIndexes,
+                             const DicomTag& tag,
+                             ValueRepresentation vr,
+                             double value) ORTHANC_OVERRIDE
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      
+    }
+
+    virtual void VisitAttribute(const std::vector<DicomTag>& parentTags,
+                                const std::vector<size_t>& parentIndexes,
+                                const DicomTag& tag,
+                                ValueRepresentation vr,
+                                const DicomTag& value) ORTHANC_OVERRIDE
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      
+    }
+
+    virtual Action VisitString(std::string& newValue,
+                               const std::vector<DicomTag>& parentTags,
+                               const std::vector<size_t>& parentIndexes,
+                               const DicomTag& tag,
+                               ValueRepresentation vr,
+                               const std::string& value) ORTHANC_OVERRIDE
+    {
+      printf("[%s] [%s]\n", FormatTag(tag).c_str(), value.c_str());
+
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      
+      return Action_None;
+    }
+  };
+}
+
+#include "../Core/SystemToolbox.h"
+
+
+TEST(DicomWebJson, Basic)
+{
+  std::string content;
+  Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/DummyCT.dcm");
+
+  Orthanc::ParsedDicomFile dicom(content);
+
+  Orthanc::DicomJsonVisitor visitor;
+  dicom.Apply(visitor);
+
+  std::cout << visitor.GetResult().toStyledString() << std::endl;
+}
+
+#endif
--- a/UnitTestsSources/ServerIndexTests.cpp	Fri Feb 01 09:28:12 2019 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Mon Feb 04 12:06:19 2019 +0100
@@ -293,8 +293,8 @@
     ASSERT_EQ("e", l.front());
   }
 
-  std::list<MetadataType> md;
-  index_->ListAvailableMetadata(md, a[4]);
+  std::map<MetadataType, std::string> md;
+  index_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(0u, md.size());
 
   index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
@@ -303,11 +303,11 @@
   index_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
   index_->SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");
   
-  index_->ListAvailableMetadata(md, a[4]);
+  index_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(1u, md.size());
-  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
+  ASSERT_EQ("PINNACLE", md[MetadataType_Instance_RemoteAet]);
   index_->SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
-  index_->ListAvailableMetadata(md, a[4]);
+  index_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(2u, md.size());
 
   std::map<MetadataType, std::string> md2;
@@ -317,9 +317,9 @@
   ASSERT_EQ("PINNACLE", md2[MetadataType_Instance_RemoteAet]);
 
   index_->DeleteMetadata(a[4], MetadataType_ModifiedFrom);
-  index_->ListAvailableMetadata(md, a[4]);
+  index_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(1u, md.size());
-  ASSERT_EQ(MetadataType_Instance_RemoteAet, md.front());
+  ASSERT_EQ("PINNACLE", md[MetadataType_Instance_RemoteAet]);
 
   index_->GetAllMetadata(md2, a[4]);
   ASSERT_EQ(1u, md2.size());