view UnitTests/ServerIndex.cpp @ 182:93ff5babcaf8

children public id
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 12 Nov 2012 10:36:58 +0100
parents 2dece1526c06
children baada606da3c
line wrap: on
line source

#include "gtest/gtest.h"

#include <ctype.h>

#include "../Core/SQLite/Connection.h"
#include "../Core/Compression/ZlibCompressor.h"
#include "../Core/DicomFormat/DicomTag.h"
#include "../Core/DicomFormat/DicomArray.h"
#include "../Core/FileStorage.h"
#include "../OrthancCppClient/HttpClient.h"
#include "../Core/HttpServer/HttpHandler.h"
#include "../Core/OrthancException.h"
#include "../Core/Toolbox.h"
#include "../Core/Uuid.h"
#include "../OrthancServer/FromDcmtkBridge.h"
#include "../OrthancServer/OrthancInitialization.h"
#include "../OrthancServer/ServerIndex.h"
#include "EmbeddedResources.h"

#include <glog/logging.h>
#include <boost/thread.hpp>


namespace Orthanc
{
  enum CompressionType
  {
    CompressionType_None = 1,
    CompressionType_Zlib = 2
  };

  enum MetadataType
  {
    MetadataType_Instance_RemoteAet = 1,
    MetadataType_Instance_IndexInSeries = 2,
    MetadataType_Series_ExpectedNumberOfInstances = 3
  };

  class IServerIndexListener
  {
  public:
    virtual ~IServerIndexListener()
    {
    }

    virtual void SignalResourceDeleted(ResourceType type,
                                       const std::string& parentPublicId) = 0;

    virtual void SignalFileDeleted(const std::string& fileUuid) = 0;                     
                                 
  };

  namespace Internals
  {
    class SignalFileDeleted : public SQLite::IScalarFunction
    {
    private:
      IServerIndexListener& listener_;

    public:
      SignalFileDeleted(IServerIndexListener& listener) :
        listener_(listener)
      {
      }

      virtual const char* GetName() const
      {
        return "SignalFileDeleted";
      }

      virtual unsigned int GetCardinality() const
      {
        return 1;
      }

      virtual void Compute(SQLite::FunctionContext& context)
      {
        listener_.SignalFileDeleted(context.GetStringValue(0));
      }
    };

    class SignalResourceDeleted : public SQLite::IScalarFunction
    {
    public:
      virtual const char* GetName() const
      {
        return "SignalResourceDeleted";
      }

      virtual unsigned int GetCardinality() const
      {
        return 2;
      }

      virtual void Compute(SQLite::FunctionContext& context)
      {
        LOG(INFO) << "A resource has been removed, of type "
                  << context.GetIntValue(0)
                  << ", with parent "
                  << context.GetIntValue(1);
      }
    };
  }


  class ServerIndexHelper
  {
  private:
    IServerIndexListener& listener_;
    SQLite::Connection db_;
    boost::mutex mutex_;

    void Open(const std::string& path);

  public:
    void SetGlobalProperty(const std::string& name,
                           const std::string& value)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
      s.BindString(0, name);
      s.BindString(1, value);
      s.Run();
    }

    bool FindGlobalProperty(std::string& target,
                            const std::string& name)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                          "SELECT value FROM GlobalProperties WHERE name=?");
      s.BindString(0, name);

      if (!s.Step())
      {
        return false;
      }
      else
      {
        target = s.ColumnString(0);
        return true;
      }
    }

    std::string GetGlobalProperty(const std::string& name,
                                  const std::string& defaultValue = "")
    {
      std::string s;
      if (FindGlobalProperty(s, name))
      {
        return s;
      }
      else
      {
        return defaultValue;
      }
    }

    int64_t CreateResource(const std::string& publicId,
                           ResourceType type)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
      s.BindInt(0, type);
      s.BindString(1, publicId);
      s.Run();
      return db_.GetLastInsertRowId();
    }

    bool FindResource(const std::string& publicId,
                      int64_t& id,
                      ResourceType& type)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                          "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
      s.BindString(0, publicId);

      if (!s.Step())
      {
        return false;
      }
      else
      {
        id = s.ColumnInt(0);
        type = static_cast<ResourceType>(s.ColumnInt(1));

        // Check whether there is a single resource with this public id
        assert(!s.Step());

        return true;
      }
    }

    void AttachChild(int64_t parent,
                     int64_t child)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
      s.BindInt(0, parent);
      s.BindInt(1, child);
      s.Run();
    }

    void DeleteResource(int64_t id)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
      s.BindInt(0, id);
      s.Run();      
    }

    void SetMetadata(int64_t id,
                     MetadataType type,
                     const std::string& value)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
      s.BindInt(0, id);
      s.BindInt(1, type);
      s.BindString(2, value);
      s.Run();
    }

    bool FindMetadata(std::string& target,
                      int64_t id,
                      MetadataType type)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                          "SELECT value FROM Metadata WHERE id=? AND type=?");
      s.BindInt(0, id);
      s.BindInt(1, type);

      if (!s.Step())
      {
        return false;
      }
      else
      {
        target = s.ColumnString(0);
        return true;
      }
    }

    std::string GetMetadata(int64_t id,
                            MetadataType type,
                            const std::string& defaultValue = "")
    {
      std::string s;
      if (FindMetadata(s, id, type))
      {
        return s;
      }
      else
      {
        return defaultValue;
      }
    }

    void AttachFile(int64_t id,
                    const std::string& name,
                    const std::string& fileUuid,
                    size_t uncompressedSize,
                    CompressionType compressionType)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?)");
      s.BindInt(0, id);
      s.BindString(1, name);
      s.BindString(2, fileUuid);
      s.BindInt(3, uncompressedSize);
      s.BindInt(4, compressionType);
      s.Run();
    }

    bool FindFile(int64_t id,
                  const std::string& name,
                  std::string& fileUuid,
                  size_t& uncompressedSize,
                  CompressionType& compressionType)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
                          "SELECT uuid, uncompressedSize, compressionType FROM AttachedFiles WHERE id=? AND name=?");
      s.BindInt(0, id);
      s.BindString(1, name);

      if (!s.Step())
      {
        return false;
      }
      else
      {
        fileUuid = s.ColumnString(0);
        uncompressedSize = s.ColumnInt(1);
        compressionType = static_cast<CompressionType>(s.ColumnInt(2));
        return true;
      }
    }

    void SetMainDicomTags(int64_t id,
                          const DicomMap& tags)
    {
      DicomArray flattened(tags);
      for (size_t i = 0; i < flattened.GetSize(); i++)
      {
        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
        s.BindInt(0, id);
        s.BindInt(1, flattened.GetElement(i).GetTag().GetGroup());
        s.BindInt(2, flattened.GetElement(i).GetTag().GetElement());
        s.BindString(3, flattened.GetElement(i).GetValue().AsString());
        s.Run();
      }
    }

    void GetMainDicomTags(DicomMap& map,
                          int64_t id)
    {
      map.Clear();

      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
      s.BindInt(0, id);
      while (s.Step())
      {
        map.SetValue(s.ColumnInt(1),
                     s.ColumnInt(2),
                     s.ColumnString(3));
      }
    }


    bool GetParentPublicId(std::string& result,
                           int64_t id)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
                          "WHERE a.internalId = b.parentId AND b.internalId = ?");     
      s.BindInt(0, id);

      if (s.Step())
      {
        result = s.ColumnString(0);
        return true;
      }
      else
      {
        return false;
      }
    }


    void GetChildrenPublicId(std::list<std::string>& result,
                             int64_t id)
    {
      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
                          "WHERE a.parentId = b.internalId AND b.internalId = ?");     
      s.BindInt(0, id);

      result.clear();

      while (s.Step())
      {
        result.push_back(s.ColumnString(0));
      }
    }
    

    int64_t GetTableRecordCount(const std::string& table)
    {
      char buf[128];
      sprintf(buf, "SELECT COUNT(*) FROM %s", table.c_str());
      SQLite::Statement s(db_, buf);

      assert(s.Step());
      int64_t c = s.ColumnInt(0);
      assert(!s.Step());

      return c;
    }

    ServerIndexHelper(const std::string& path,
                      IServerIndexListener& listener) :
      listener_(listener)
    {
      Open(path);
    }

    ServerIndexHelper(IServerIndexListener& listener) :
      listener_(listener)
    {
      Open("");
    }
  };



  void ServerIndexHelper::Open(const std::string& path)
  {
    if (path == "")
    {
      db_.OpenInMemory();
    }
    else
    {
      db_.Open(path);
    }

    if (!db_.DoesTableExist("GlobalProperties"))
    {
      LOG(INFO) << "Creating the database";
      std::string query;
      EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE_2);
      db_.Execute(query);
    }

    db_.Register(new Internals::SignalFileDeleted(listener_));
    db_.Register(new Internals::SignalResourceDeleted);
  }


  class ServerIndexListener : public IServerIndexListener
  {
  public:
    virtual void SignalResourceDeleted(ResourceType type,
                                       const std::string& parentPublicId) 
    {
    }

    virtual void SignalFileDeleted(const std::string& fileUuid)
    {
      LOG(INFO) << "A file must be removed: " << fileUuid;
    }                                
  };

  /*
  class ServerIndex2
  {
  private:
    ServerIndexListener listener_;
    ServerIndexHelper helper_;

    void Open(const std::string& storagePath)
    {
      boost::filesystem::path p = storagePath;

      try
      {
        boost::filesystem::create_directories(storagePath);
      }
      catch (boost::filesystem::filesystem_error)
      {
      }

      p /= "index";
    }

  public:
    ServerIndexHelper(const std::string& storagePath) :
      helper_(storagePath)
    {
      Open(storagePath);
    }
  };
  */
}



using namespace Orthanc;

TEST(ServerIndexHelper, Simple)
{
  ServerIndexListener listener;
  /*Toolbox::RemoveFile("toto");
    ServerIndexHelper index("toto", listener);*/
  ServerIndexHelper index(listener);

  LOG(WARNING) << "ok";

  int64_t a[] = {
    index.CreateResource("a", ResourceType_Patient),   // 0
    index.CreateResource("b", ResourceType_Study),     // 1
    index.CreateResource("c", ResourceType_Series),    // 2
    index.CreateResource("d", ResourceType_Instance),  // 3
    index.CreateResource("e", ResourceType_Instance),  // 4
    index.CreateResource("f", ResourceType_Instance),  // 5
    index.CreateResource("g", ResourceType_Study)      // 6
  };

  index.SetGlobalProperty("Hello", "World");

  index.AttachChild(a[0], a[1]);
  index.AttachChild(a[1], a[2]);
  index.AttachChild(a[2], a[3]);
  index.AttachChild(a[2], a[4]);
  index.AttachChild(a[6], a[5]);

  std::string s;
  
  ASSERT_FALSE(index.GetParentPublicId(s, a[0]));
  ASSERT_FALSE(index.GetParentPublicId(s, a[6]));
  ASSERT_TRUE(index.GetParentPublicId(s, a[1])); ASSERT_EQ("a", s);
  ASSERT_TRUE(index.GetParentPublicId(s, a[2])); ASSERT_EQ("b", s);
  ASSERT_TRUE(index.GetParentPublicId(s, a[3])); ASSERT_EQ("c", s);
  ASSERT_TRUE(index.GetParentPublicId(s, a[4])); ASSERT_EQ("c", s);
  ASSERT_TRUE(index.GetParentPublicId(s, a[5])); ASSERT_EQ("g", s);

  std::list<std::string> l;
  index.GetChildrenPublicId(l, a[0]); ASSERT_EQ(1, l.size()); ASSERT_EQ("b", l.front());
  index.GetChildrenPublicId(l, a[1]); ASSERT_EQ(1, l.size()); ASSERT_EQ("c", l.front());
  index.GetChildrenPublicId(l, a[3]); ASSERT_EQ(0, l.size()); 
  index.GetChildrenPublicId(l, a[4]); ASSERT_EQ(0, l.size()); 
  index.GetChildrenPublicId(l, a[5]); ASSERT_EQ(0, l.size()); 
  index.GetChildrenPublicId(l, a[6]); ASSERT_EQ(1, l.size()); ASSERT_EQ("f", l.front());

  index.GetChildrenPublicId(l, a[2]); ASSERT_EQ(2, l.size()); 
  if (l.front() == "d")
  {
    ASSERT_EQ("e", l.back());
  }
  else
  {
    ASSERT_EQ("d", l.back());
    ASSERT_EQ("e", l.front());
  }


  index.AttachFile(a[4], "_json", "my json file", 42, CompressionType_Zlib);
  index.AttachFile(a[4], "_dicom", "my dicom file", 42, CompressionType_None);
  index.SetMetadata(a[4], MetadataType_Instance_RemoteAet, "PINNACLE");

  DicomMap m;
  m.SetValue(0x0010, 0x0010, "PatientName");
  index.SetMainDicomTags(a[3], m);

  int64_t b;
  ResourceType t;
  ASSERT_TRUE(index.FindResource("g", b, t));
  ASSERT_EQ(7, b);
  ASSERT_EQ(ResourceType_Study, t);

  ASSERT_TRUE(index.FindMetadata(s, a[4], MetadataType_Instance_RemoteAet));
  ASSERT_FALSE(index.FindMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
  ASSERT_EQ("PINNACLE", s);
  ASSERT_EQ("PINNACLE", index.GetMetadata(a[4], MetadataType_Instance_RemoteAet));
  ASSERT_EQ("None", index.GetMetadata(a[4], MetadataType_Instance_IndexInSeries, "None"));

  ASSERT_TRUE(index.FindGlobalProperty(s, "Hello"));
  ASSERT_FALSE(index.FindGlobalProperty(s, "Hello2"));
  ASSERT_EQ("World", s);
  ASSERT_EQ("World", index.GetGlobalProperty("Hello"));
  ASSERT_EQ("None", index.GetGlobalProperty("Hello2", "None"));

  size_t us;
  CompressionType ct;
  ASSERT_TRUE(index.FindFile(a[4], "_json", s, us, ct));
  ASSERT_EQ("my json file", s);
  ASSERT_EQ(42, us);
  ASSERT_EQ(CompressionType_Zlib, ct);

  ASSERT_EQ(7, index.GetTableRecordCount("Resources"));
  ASSERT_EQ(2, index.GetTableRecordCount("AttachedFiles"));
  ASSERT_EQ(1, index.GetTableRecordCount("Metadata"));
  ASSERT_EQ(1, index.GetTableRecordCount("MainDicomTags"));
  index.DeleteResource(a[0]);
  ASSERT_EQ(2, index.GetTableRecordCount("Resources"));
  ASSERT_EQ(0, index.GetTableRecordCount("Metadata"));
  ASSERT_EQ(0, index.GetTableRecordCount("AttachedFiles"));
  ASSERT_EQ(0, index.GetTableRecordCount("MainDicomTags"));
  index.DeleteResource(a[6]);
  ASSERT_EQ(0, index.GetTableRecordCount("Resources"));
  ASSERT_EQ(1, index.GetTableRecordCount("GlobalProperties"));
}