changeset 1018:564e39d6df13 lua-scripting

integration mainline->lua-scripting
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Jul 2014 11:31:14 +0200
parents 9d0c7301596e (current diff) f4bbf13572cd (diff)
children cd8569f7dd21
files CMakeLists.txt OrthancServer/ParsedDicomFile.cpp OrthancServer/main.cpp Resources/Configuration.json
diffstat 48 files changed, 5176 insertions(+), 371 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jul 10 10:43:47 2014 +0200
+++ b/CMakeLists.txt	Thu Jul 10 11:31:14 2014 +0200
@@ -118,6 +118,10 @@
   OrthancCppClient/Series.cpp
   OrthancCppClient/Instance.cpp
   OrthancCppClient/Patient.cpp
+
+  Plugins/Engine/SharedLibrary.cpp
+  Plugins/Engine/PluginsManager.cpp
+  Plugins/Engine/PluginsHttpHandler.cpp
   )
 
 
@@ -182,6 +186,8 @@
   UnitTestsSources/UnitTestsMain.cpp
   UnitTestsSources/ImageProcessingTests.cpp
   UnitTestsSources/JpegLosslessTests.cpp
+
+  UnitTestsSources/PluginsTests.cpp
   )
 
 
@@ -475,8 +481,15 @@
     ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen
     @ONLY)
 
+  configure_file(
+    ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen
+    @ONLY)
+
   add_custom_target(doc
     ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen
+    COMMAND
+    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen
     WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
     COMMENT "Generating internal documentation with Doxygen" VERBATIM
     )
--- a/Core/Enumerations.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/Enumerations.h	Thu Jul 10 11:31:14 2014 +0200
@@ -71,7 +71,8 @@
     ErrorCode_InexistentTag,
     ErrorCode_ReadOnly,
     ErrorCode_IncompatibleImageFormat,
-    ErrorCode_IncompatibleImageSize
+    ErrorCode_IncompatibleImageSize,
+    ErrorCode_SharedLibrary
   };
 
   /**
--- a/Core/HttpServer/BufferHttpSender.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Thu Jul 10 11:31:14 2014 +0200
@@ -49,7 +49,9 @@
     virtual bool SendData(HttpOutput& output)
     {
       if (buffer_.size())
-        output.Send(&buffer_[0], buffer_.size());
+      {
+        output.SendBodyData(&buffer_[0], buffer_.size());
+      }
 
       return true;
     }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -51,13 +51,7 @@
   }
 
 
-  bool EmbeddedResourceHttpHandler::IsServedUri(const UriComponents& uri)
-  {
-    return Toolbox::IsChildUri(baseUri_, uri);
-  }
-
-
-  void EmbeddedResourceHttpHandler::Handle(
+  bool EmbeddedResourceHttpHandler::Handle(
     HttpOutput& output,
     HttpMethod method,
     const UriComponents& uri,
@@ -65,10 +59,16 @@
     const Arguments& arguments,
     const std::string&)
   {
+    if (!Toolbox::IsChildUri(baseUri_, uri))
+    {
+      // This URI is not served by this handler
+      return false;
+    }
+
     if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
-      return;
+      return true;
     }
 
     std::string resourcePath = Toolbox::FlattenUri(uri, baseUri_.size());
@@ -85,5 +85,7 @@
       LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath;
       output.SendHeader(HttpStatus_404_NotFound);
     }
+
+    return true;
   } 
 }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Jul 10 11:31:14 2014 +0200
@@ -50,9 +50,7 @@
       const std::string& baseUri,
       EmbeddedResources::DirectoryResourceId resourceId);
 
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(
+    virtual bool Handle(
       HttpOutput& output,
       HttpMethod method,
       const UriComponents& uri,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -56,15 +56,15 @@
     namespace fs = boost::filesystem;
 
     output.SendOkHeader("text/html", false, 0, NULL);
-    output.SendString("<html>");
-    output.SendString("  <body>");
-    output.SendString("    <h1>Subdirectories</h1>");
-    output.SendString("    <ul>");
+    output.SendBodyString("<html>");
+    output.SendBodyString("  <body>");
+    output.SendBodyString("    <h1>Subdirectories</h1>");
+    output.SendBodyString("    <ul>");
 
     if (uri.size() > 0)
     {
       std::string h = Toolbox::FlattenUri(uri) + "/..";
-      output.SendString("<li><a href=\"" + h + "\">..</a></li>");
+      output.SendBodyString("<li><a href=\"" + h + "\">..</a></li>");
     }
 
     fs::directory_iterator end;
@@ -78,12 +78,12 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_directory(it->status()))
-        output.SendString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
     }      
 
-    output.SendString("    </ul>");      
-    output.SendString("    <h1>Files</h1>");
-    output.SendString("    <ul>");
+    output.SendBodyString("    </ul>");      
+    output.SendBodyString("    <h1>Files</h1>");
+    output.SendBodyString("    <ul>");
 
     for (fs::directory_iterator it(p) ; it != end; ++it)
     {
@@ -95,12 +95,12 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_regular_file(it->status()))
-        output.SendString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
     }      
 
-    output.SendString("    </ul>");
-    output.SendString("  </body>");
-    output.SendString("</html>");
+    output.SendBodyString("    </ul>");
+    output.SendBodyString("  </body>");
+    output.SendBodyString("</html>");
   }
 
 
@@ -120,13 +120,7 @@
   }
 
 
-  bool FilesystemHttpHandler::IsServedUri(const UriComponents& uri)
-  {
-    return Toolbox::IsChildUri(pimpl_->baseUri_, uri);
-  }
-
-
-  void FilesystemHttpHandler::Handle(
+  bool FilesystemHttpHandler::Handle(
     HttpOutput& output,
     HttpMethod method,
     const UriComponents& uri,
@@ -134,10 +128,16 @@
     const Arguments& arguments,
     const std::string&)
   {
+    if (!Toolbox::IsChildUri(pimpl_->baseUri_, uri))
+    {
+      // This URI is not served by this handler
+      return false;
+    }
+
     if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
-      return;
+      return true;
     }
 
     namespace fs = boost::filesystem;
@@ -164,5 +164,7 @@
     {
       output.SendHeader(HttpStatus_404_NotFound);
     }
+
+    return true;
   } 
 }
--- a/Core/HttpServer/FilesystemHttpHandler.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Thu Jul 10 11:31:14 2014 +0200
@@ -52,9 +52,7 @@
     FilesystemHttpHandler(const std::string& baseUri,
                           const std::string& root);
 
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(
+    virtual bool Handle(
       HttpOutput& output,
       HttpMethod method,
       const UriComponents& uri,
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -73,7 +73,7 @@
       }
       else
       {
-        output.Send(&buffer[0], nbytes);
+        output.SendBodyData(&buffer[0], nbytes);
       }
     }
 
--- a/Core/HttpServer/HttpHandler.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -34,6 +34,8 @@
 #include "HttpHandler.h"
 
 #include <string.h>
+#include <iostream>
+
 
 namespace Orthanc
 {
@@ -62,7 +64,7 @@
   }
 
 
-  void HttpHandler::ParseGetQuery(HttpHandler::Arguments& result, const char* query)
+  void HttpHandler::ParseGetArguments(HttpHandler::Arguments& result, const char* query)
   {
     const char* pos = query;
 
@@ -84,6 +86,24 @@
   }
 
 
+  void  HttpHandler::ParseGetQuery(UriComponents& uri,
+                                   HttpHandler::Arguments& getArguments, 
+                                   const char* query)
+  {
+    const char *questionMark = ::strchr(query, '?');
+    if (questionMark == NULL)
+    {
+      // No question mark in the string
+      Toolbox::SplitUriComponents(uri, query);
+      getArguments.clear();
+    }
+    else
+    {
+      Toolbox::SplitUriComponents(uri, std::string(query, questionMark));
+      HttpHandler::ParseGetArguments(getArguments, questionMark + 1);
+    }    
+  }
+
 
   std::string HttpHandler::GetArgument(const Arguments& getArguments,
                                        const std::string& name,
--- a/Core/HttpServer/HttpHandler.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/HttpHandler.h	Thu Jul 10 11:31:14 2014 +0200
@@ -50,16 +50,18 @@
     {
     }
 
-    virtual bool IsServedUri(const UriComponents& uri) = 0;
-
-    virtual void Handle(HttpOutput& output,
+    virtual bool Handle(HttpOutput& output,
                         HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
                         const Arguments& getArguments,
                         const std::string& postData) = 0;
 
-    static void ParseGetQuery(HttpHandler::Arguments& result, 
+    static void ParseGetArguments(HttpHandler::Arguments& result, 
+                                  const char* query);
+
+    static void ParseGetQuery(UriComponents& uri,
+                              HttpHandler::Arguments& getArguments, 
                               const char* query);
 
     static std::string GetArgument(const Arguments& getArguments,
--- a/Core/HttpServer/HttpOutput.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -42,14 +42,71 @@
 
 namespace Orthanc
 {
-  void HttpOutput::SendString(const std::string& s)
+  void HttpOutput::StateMachine::SendHttpStatus(HttpStatus status)
   {
-    if (s.size() > 0)
+    if (state_ != State_WaitingHttpStatus)
     {
-      Send(&s[0], s.size());
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    stream_.OnHttpStatusReceived(status);
+    state_ = State_WritingHeader;
+
+    std::string s = "HTTP/1.1 " + 
+      boost::lexical_cast<std::string>(status) +
+      " " + std::string(EnumerationToString(status)) +
+      "\r\n";
+
+    stream_.Send(true, &s[0], s.size());
+  }
+
+  void HttpOutput::StateMachine::SendHeaderData(const void* buffer, size_t length)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    stream_.Send(true, buffer, length);
+  }
+
+  void HttpOutput::StateMachine::SendHeaderString(const std::string& str)
+  {
+    if (str.size() > 0)
+    {
+      SendHeaderData(&str[0], str.size());
     }
   }
 
+  void HttpOutput::StateMachine::SendBodyData(const void* buffer, size_t length)
+  {
+    if (state_ == State_WaitingHttpStatus)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (state_ == State_WritingHeader)
+    {
+      // Close the HTTP header before writing the body
+      stream_.Send(true, "\r\n", 2);
+      state_ = State_WritingBody;
+    }
+
+    if (length > 0)
+    {
+      stream_.Send(false, buffer, length);
+    }
+  }
+
+  void HttpOutput::StateMachine::SendBodyString(const std::string& str)
+  {
+    if (str.size() > 0)
+    {
+      SendBodyData(&str[0], str.size());
+    }
+  }
+
+
   void HttpOutput::PrepareOkHeader(Header& header,
                                    const char* contentType,
                                    bool hasContentLength,
@@ -87,49 +144,37 @@
 
   void HttpOutput::SendOkHeader(const Header& header)
   {
-    std::string s = "HTTP/1.1 200 OK\r\n";
+    stateMachine_.SendHttpStatus(HttpStatus_200_Ok);
 
+    std::string s;
     for (Header::const_iterator 
            it = header.begin(); it != header.end(); ++it)
     {
       s += it->first + ": " + it->second + "\r\n";
     }
 
-    s += "\r\n";
-
-    Send(&s[0], s.size());
+    stateMachine_.SendHeaderString(s);
   }
 
 
   void HttpOutput::SendMethodNotAllowedError(const std::string& allowed)
   {
-    std::string s = 
-      "HTTP/1.1 405 " + std::string(EnumerationToString(HttpStatus_405_MethodNotAllowed)) +
-      "\r\nAllow: " + allowed + 
-      "\r\n\r\n";
-    Send(&s[0], s.size());
+    stateMachine_.SendHttpStatus(HttpStatus_405_MethodNotAllowed);
+    stateMachine_.SendHeaderString("Allow: " + allowed + "\r\n");
   }
 
 
   void HttpOutput::SendHeader(HttpStatus status)
   {
     if (status == HttpStatus_200_Ok ||
+        status == HttpStatus_301_MovedPermanently ||
+        status == HttpStatus_401_Unauthorized ||
         status == HttpStatus_405_MethodNotAllowed)
     {
       throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput");
     }
     
-    SendHeaderInternal(status);
-  }
-
-
-  void HttpOutput::SendHeaderInternal(HttpStatus status)
-  {
-    std::string s = "HTTP/1.1 " + 
-      boost::lexical_cast<std::string>(status) +
-      " " + std::string(EnumerationToString(status)) +
-      "\r\n\r\n";
-    Send(&s[0], s.size());
+    stateMachine_.SendHttpStatus(status);
   }
 
 
@@ -137,7 +182,7 @@
                                                const std::string& contentType)
   {
     SendOkHeader(contentType.c_str(), true, buffer.size(), NULL);
-    SendString(buffer);
+    SendBodyString(buffer);
   }
 
 
@@ -160,7 +205,7 @@
     PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL);
     PrepareCookies(header, cookies);
     SendOkHeader(header);
-    SendString(buffer);
+    SendBodyString(buffer);
   }
 
 
@@ -169,7 +214,7 @@
                                                const std::string& contentType)
   {
     SendOkHeader(contentType.c_str(), true, size, NULL);
-    Send(buffer, size);
+    SendBodyData(buffer, size);
   }
 
 
@@ -182,17 +227,21 @@
     PrepareOkHeader(header, contentType.c_str(), true, size, NULL);
     PrepareCookies(header, cookies);
     SendOkHeader(header);
-    Send(buffer, size);
+    SendBodyData(buffer, size);
   }
 
 
-
   void HttpOutput::Redirect(const std::string& path)
   {
-    std::string s = 
-      "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + 
-      "\r\nLocation: " + path +
-      "\r\n\r\n";
-    Send(&s[0], s.size());  
+    stateMachine_.SendHttpStatus(HttpStatus_301_MovedPermanently);
+    stateMachine_.SendHeaderString("Location: " + path + "\r\n");
   }
+
+
+  void HttpOutput::SendUnauthorized(const std::string& realm)
+  {
+    stateMachine_.SendHttpStatus(HttpStatus_401_Unauthorized);
+    stateMachine_.SendHeaderString("WWW-Authenticate: Basic realm=\"" + realm + "\"\r\n");
+  }
+
 }
--- a/Core/HttpServer/HttpOutput.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/HttpOutput.h	Thu Jul 10 11:31:14 2014 +0200
@@ -36,16 +36,46 @@
 #include <string>
 #include <stdint.h>
 #include "../Enumerations.h"
+#include "IHttpOutputStream.h"
 #include "HttpHandler.h"
 
 namespace Orthanc
 {
-  class HttpOutput
+  class HttpOutput : public boost::noncopyable
   {
   private:
     typedef std::list< std::pair<std::string, std::string> >  Header;
 
-    void SendHeaderInternal(HttpStatus status);
+    class StateMachine : public boost::noncopyable
+    {
+    private:
+      enum State
+      {
+        State_WaitingHttpStatus,
+        State_WritingHeader,      
+        State_WritingBody
+      };
+
+      IHttpOutputStream& stream_;
+      State state_;
+
+    public:
+      StateMachine(IHttpOutputStream& stream) : 
+        stream_(stream),
+        state_(State_WaitingHttpStatus)
+      {
+      }
+
+      void SendHttpStatus(HttpStatus status);
+
+      void SendHeaderData(const void* buffer, size_t length);
+
+      void SendHeaderString(const std::string& str);
+
+      void SendBodyData(const void* buffer, size_t length);
+
+      void SendBodyString(const std::string& str);
+    };
 
     void PrepareOkHeader(Header& header,
                          const char* contentType,
@@ -58,19 +88,27 @@
     void PrepareCookies(Header& header,
                         const HttpHandler::Arguments& cookies);
 
+    StateMachine stateMachine_;
+
   public:
-    virtual ~HttpOutput()
+    HttpOutput(IHttpOutputStream& stream) : stateMachine_(stream)
     {
     }
 
-    virtual void Send(const void* buffer, size_t length) = 0;
-
     void SendOkHeader(const char* contentType,
                       bool hasContentLength,
                       uint64_t contentLength,
                       const char* contentFilename);
 
-    void SendString(const std::string& s);
+    void SendBodyData(const void* buffer, size_t length)
+    {
+      stateMachine_.SendBodyData(buffer, length);
+    }
+
+    void SendBodyString(const std::string& str)
+    {
+      stateMachine_.SendBodyString(str);
+    }
 
     void SendMethodNotAllowedError(const std::string& allowed);
 
@@ -78,6 +116,8 @@
 
     void Redirect(const std::string& path);
 
+    void SendUnauthorized(const std::string& realm);
+
     // Higher-level constructs to send entire buffers ----------------------------
 
     void AnswerBufferWithContentType(const std::string& buffer,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/IHttpOutputStream.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "../Enumerations.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IHttpOutputStream : public boost::noncopyable
+  {
+  public:
+    virtual ~IHttpOutputStream()
+    {
+    }
+
+    virtual void OnHttpStatusReceived(HttpStatus status) = 0;
+
+    virtual void Send(bool isHeader, const void* buffer, size_t length) = 0;
+  };
+}
--- a/Core/HttpServer/MongooseServer.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -68,23 +68,28 @@
   namespace
   {
     // Anonymous namespace to avoid clashes between compilation modules
-    class MongooseOutput : public HttpOutput
+    class MongooseOutputStream : public IHttpOutputStream
     {
     private:
       struct mg_connection* connection_;
 
     public:
-      MongooseOutput(struct mg_connection* connection) : connection_(connection)
+      MongooseOutputStream(struct mg_connection* connection) : connection_(connection)
       {
       }
 
-      virtual void Send(const void* buffer, size_t length)
+      virtual void Send(bool isHeader, const void* buffer, size_t length)
       {
         if (length > 0)
         {
           mg_write(connection_, buffer, length);
         }
       }
+
+      virtual void OnHttpStatusReceived(HttpStatus status)
+      {
+        // Ignore this
+      }
     };
 
 
@@ -255,23 +260,6 @@
 
 
 
-  HttpHandler* MongooseServer::FindHandler(const UriComponents& forUri) const
-  {
-    for (Handlers::const_iterator it = 
-           handlers_.begin(); it != handlers_.end(); ++it) 
-    {
-      if ((*it)->IsServedUri(forUri))
-      {
-        return *it;
-      }
-    }
-
-    return NULL;
-  }
-
-
-
-
   static PostDataStatus ReadBody(std::string& postData,
                                  struct mg_connection *connection,
                                  const HttpHandler::Arguments& headers)
@@ -421,15 +409,6 @@
   }
 
 
-  static void SendUnauthorized(HttpOutput& output)
-  {
-    std::string s = "HTTP/1.1 401 Unauthorized\r\n" 
-      "WWW-Authenticate: Basic realm=\"" ORTHANC_REALM "\""
-      "\r\n\r\n";
-    output.Send(&s[0], s.size());
-  }
-
-
   static bool Authorize(const MongooseServer& that,
                         const HttpHandler::Arguments& headers,
                         HttpOutput& output)
@@ -449,7 +428,7 @@
 
     if (!granted)
     {
-      SendUnauthorized(output);
+      output.SendUnauthorized(ORTHANC_REALM);
       return false;
     }
     else
@@ -576,13 +555,14 @@
     if (event == MG_NEW_REQUEST) 
     {
       MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data);
-      MongooseOutput output(connection);
+      MongooseOutputStream stream(connection);
+      HttpOutput output(stream);
 
       // Check remote calls
       if (!that->IsRemoteAccessAllowed() &&
           request->remote_ip != LOCALHOST)
       {
-        SendUnauthorized(output);
+        output.SendUnauthorized(ORTHANC_REALM);
         return (void*) "";
       }
 
@@ -601,7 +581,7 @@
       HttpHandler::Arguments argumentsGET;
       if (!strcmp(request->request_method, "GET"))
       {
-        HttpHandler::ParseGetQuery(argumentsGET, request->query_string);
+        HttpHandler::ParseGetArguments(argumentsGET, request->query_string);
       }
 
 
@@ -637,7 +617,7 @@
 
         if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str()))
         {
-          SendUnauthorized(output);
+          output.SendUnauthorized(ORTHANC_REALM);
           return (void*) "";
         }
       }
@@ -690,7 +670,7 @@
       }
 
 
-      // Call the proper handler for this URI
+      // Decompose the URI into its components
       UriComponents uri;
       try
       {
@@ -703,31 +683,36 @@
       }
 
 
-      HttpHandler* handler = that->FindHandler(uri);
-      if (handler)
+      // Loop over the candidate handlers for this URI
+      LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
+      bool found = false;
+
+      for (MongooseServer::Handlers::const_iterator it = 
+             that->GetHandlers().begin(); it != that->GetHandlers().end() && !found; ++it) 
       {
         try
         {
-          LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
-          handler->Handle(output, method, uri, headers, argumentsGET, body);
+          found = (*it)->Handle(output, method, uri, headers, argumentsGET, body);
         }
         catch (OrthancException& e)
         {
-          LOG(ERROR) << "MongooseServer Exception [" << e.What() << "]";
-          output.SendHeader(HttpStatus_500_InternalServerError);        
+          // Using this candidate handler results in an exception
+          LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
+          return (void*) "";
         }
         catch (boost::bad_lexical_cast&)
         {
-          LOG(ERROR) << "MongooseServer Exception: Bad lexical cast";
-          output.SendHeader(HttpStatus_400_BadRequest);
+          LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast";
+          return (void*) "";
         }
         catch (std::runtime_error&)
         {
-          LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request";
-          output.SendHeader(HttpStatus_400_BadRequest);
+          LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request";
+          return (void*) "";
         }
       }
-      else
+      
+      if (!found)
       {
         output.SendHeader(HttpStatus_404_NotFound);
       }
@@ -818,23 +803,17 @@
   }
 
 
-  void MongooseServer::RegisterHandler(HttpHandler* handler)
+  void MongooseServer::RegisterHandler(HttpHandler& handler)
   {
     Stop();
 
-    handlers_.push_back(handler);
+    handlers_.push_back(&handler);
   }
 
 
   void MongooseServer::ClearHandlers()
   {
     Stop();
-
-    for (Handlers::iterator it = 
-           handlers_.begin(); it != handlers_.end(); ++it)
-    {
-      delete *it;
-    }
   }
 
 
--- a/Core/HttpServer/MongooseServer.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/HttpServer/MongooseServer.h	Thu Jul 10 11:31:14 2014 +0200
@@ -59,12 +59,14 @@
 
   class MongooseServer
   {
+  public:
+    typedef std::list<HttpHandler*> Handlers;
+
   private:
     // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
-    typedef std::list<HttpHandler*> Handlers;
     Handlers handlers_;
 
     typedef std::set<std::string> RegisteredUsers;
@@ -100,7 +102,7 @@
     void RegisterUser(const char* username,
                       const char* password);
 
-    void RegisterHandler(HttpHandler* handler);  // This takes the ownership
+    void RegisterHandler(HttpHandler& handler);
 
     bool IsAuthenticationEnabled() const
     {
@@ -139,11 +141,13 @@
 
     void ClearHandlers();
 
-    // Can return NULL if no handler is associated to this URI
-    HttpHandler* FindHandler(const UriComponents& forUri) const;
-
     ChunkStore& GetChunkStore();
 
     bool IsValidBasicHttpAuthentication(const std::string& basic) const;
+
+    const Handlers& GetHandlers() const
+    {
+      return handlers_;
+    }
   };
 }
--- a/Core/ImageFormats/ImageAccessor.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -168,7 +168,7 @@
     pitch_ = pitch;
     buffer_ = const_cast<void*>(buffer);
 
-    assert(GetBytesPerPixel(format_) * width_ <= pitch_);
+    assert(GetBytesPerPixel() * width_ <= pitch_);
   }
 
 
@@ -185,7 +185,7 @@
     pitch_ = pitch;
     buffer_ = buffer;
 
-    assert(GetBytesPerPixel(format_) * width_ <= pitch_);
+    assert(GetBytesPerPixel() * width_ <= pitch_);
   }
 
 
--- a/Core/ImageFormats/ImageAccessor.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.h	Thu Jul 10 11:31:14 2014 +0200
@@ -62,6 +62,11 @@
       return format_;
     }
 
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
     unsigned int GetWidth() const
     {
       return width_;
--- a/Core/ImageFormats/ImageProcessing.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/ImageFormats/ImageProcessing.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -75,6 +75,44 @@
   }
 
 
+  template <typename TargetType>
+  static void ConvertColorToGrayscale(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
+      {
+        // Y = 0.2126 R + 0.7152 G + 0.0722 B
+        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
+                     7152 * static_cast<int32_t>(s[1]) +
+                     0722 * static_cast<int32_t>(s[2])) / 1000;
+        
+        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(v);
+        }
+      }
+    }
+  }
+
+
   template <typename PixelType>
   static void SetInternal(ImageAccessor& image,
                           int64_t constant)
@@ -319,6 +357,27 @@
       return;
     }
 
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<int16_t>(target, source);
+      return;
+    }
+
     throw OrthancException(ErrorCode_NotImplemented);
   }
 
--- a/Core/OrthancException.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/OrthancException.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -121,6 +121,9 @@
       case ErrorCode_IncompatibleImageFormat:
         return "Incompatible format of the images";
 
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/RestApi/RestApi.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/RestApi/RestApi.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -36,63 +36,89 @@
 #include <stdlib.h>   // To define "_exit()" under Windows
 #include <glog/logging.h>
 
+#include <stdio.h>
+
 namespace Orthanc
 {
-  bool RestApi::IsGetAccepted(const UriComponents& uri)
+  namespace
   {
-    for (GetHandlers::const_iterator it = getHandlers_.begin();
-         it != getHandlers_.end(); ++it)
+    // Anonymous namespace to avoid clashes between compilation modules
+    class HttpHandlerVisitor : public RestApiHierarchy::IVisitor
     {
-      if (it->first->Match(uri))
+    private:
+      RestApi& api_;
+      HttpOutput& output_;
+      HttpMethod method_;
+      const HttpHandler::Arguments& headers_;
+      const HttpHandler::Arguments& getArguments_;
+      const std::string& postData_;
+
+    public:
+      HttpHandlerVisitor(RestApi& api,
+                         HttpOutput& output,
+                         HttpMethod method,
+                         const HttpHandler::Arguments& headers,
+                         const HttpHandler::Arguments& getArguments,
+                         const std::string& postData) :
+        api_(api),
+        output_(output),
+        method_(method),
+        headers_(headers),
+        getArguments_(getArguments),
+        postData_(postData)
       {
-        return true;
       }
-    }
+
+      virtual bool Visit(const RestApiHierarchy::Resource& resource,
+                         const UriComponents& uri,
+                         const HttpHandler::Arguments& components,
+                         const UriComponents& trailing)
+      {
+        if (resource.HasHandler(method_))
+        {
+          RestApiOutput output(output_);
+
+          switch (method_)
+          {
+            case HttpMethod_Get:
+            {
+              RestApiGetCall call(output, api_, headers_, components, trailing, uri, getArguments_);
+              resource.Handle(call);
+              return true;
+            }
 
-    return false;
+            case HttpMethod_Post:
+            {
+              RestApiPostCall call(output, api_, headers_, components, trailing, uri, postData_);
+              resource.Handle(call);
+              return true;
+            }
+
+            case HttpMethod_Delete:
+            {
+              RestApiDeleteCall call(output, api_, headers_, components, trailing, uri);
+              resource.Handle(call);
+              return true;
+            }
+
+            case HttpMethod_Put:
+            {
+              RestApiPutCall call(output, api_, headers_, components, trailing, uri, postData_);
+              resource.Handle(call);
+              return true;
+            }
+
+            default:
+              return false;
+          }
+        }
+
+        return false;
+      }
+    };
   }
 
-  bool RestApi::IsPutAccepted(const UriComponents& uri)
-  {
-    for (PutHandlers::const_iterator it = putHandlers_.begin();
-         it != putHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
 
-    return false;
-  }
-
-  bool RestApi::IsPostAccepted(const UriComponents& uri)
-  {
-    for (PostHandlers::const_iterator it = postHandlers_.begin();
-         it != postHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool RestApi::IsDeleteAccepted(const UriComponents& uri)
-  {
-    for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-         it != deleteHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
 
   static void AddMethod(std::string& target,
                         const std::string& method)
@@ -103,158 +129,96 @@
       target = method;
   }
 
-  std::string  RestApi::GetAcceptedMethods(const UriComponents& uri)
+  static std::string  MethodsToString(const std::set<HttpMethod>& methods)
   {
     std::string s;
 
-    if (IsGetAccepted(uri))
+    if (methods.find(HttpMethod_Get) != methods.end())
+    {
       AddMethod(s, "GET");
+    }
 
-    if (IsPutAccepted(uri))
-      AddMethod(s, "PUT");
+    if (methods.find(HttpMethod_Post) != methods.end())
+    {
+      AddMethod(s, "POST");
+    }
 
-    if (IsPostAccepted(uri))
-      AddMethod(s, "POST");
+    if (methods.find(HttpMethod_Put) != methods.end())
+    {
+      AddMethod(s, "PUT");
+    }
 
-    if (IsDeleteAccepted(uri))
+    if (methods.find(HttpMethod_Delete) != methods.end())
+    {
       AddMethod(s, "DELETE");
+    }
 
     return s;
   }
 
-  RestApi::~RestApi()
-  {
-    for (GetHandlers::iterator it = getHandlers_.begin(); 
-         it != getHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
 
-    for (PutHandlers::iterator it = putHandlers_.begin(); 
-         it != putHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
 
-    for (PostHandlers::iterator it = postHandlers_.begin(); 
-         it != postHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
-
-    for (DeleteHandlers::iterator it = deleteHandlers_.begin(); 
-         it != deleteHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
-  }
-
-  bool RestApi::IsServedUri(const UriComponents& uri)
-  {
-    return (IsGetAccepted(uri) ||
-            IsPutAccepted(uri) ||
-            IsPostAccepted(uri) ||
-            IsDeleteAccepted(uri));
-  }
-
-  void RestApi::Handle(HttpOutput& output,
+  bool RestApi::Handle(HttpOutput& output,
                        HttpMethod method,
                        const UriComponents& uri,
                        const Arguments& headers,
                        const Arguments& getArguments,
                        const std::string& postData)
   {
-    bool ok = false;
-    RestApiOutput restOutput(output);
-    HttpHandler::Arguments components;
-    UriComponents trailing;
+    HttpHandlerVisitor visitor(*this, output, method, headers, getArguments, postData);
 
-    if (method == HttpMethod_Get)
-    {
-      for (GetHandlers::const_iterator it = getHandlers_.begin();
-           it != getHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          RestApiGetCall call(restOutput, *this, headers, components, trailing, uri, getArguments);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Put)
+    if (root_.LookupResource(uri, visitor))
     {
-      for (PutHandlers::const_iterator it = putHandlers_.begin();
-           it != putHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          RestApiPutCall call(restOutput, *this, headers, components, trailing, uri, postData);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Post)
-    {
-      for (PostHandlers::const_iterator it = postHandlers_.begin();
-           it != postHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          RestApiPostCall call(restOutput, *this, headers, components, trailing, uri, postData);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Delete)
-    {
-      for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-           it != deleteHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          RestApiDeleteCall call(restOutput, *this, headers, components, trailing, uri);
-          it->second(call);
-        }
-      }
+      return true;
     }
 
-    if (!ok)
+    Json::Value directory;
+    if (root_.GetDirectory(directory, uri))
+    {
+      RestApiOutput tmp(output);
+      tmp.AnswerJson(directory);
+      return true;
+    }
+
+    std::set<HttpMethod> methods;
+    root_.GetAcceptedMethods(methods, uri);
+
+    if (methods.empty())
+    {
+      return false;  // This URI is not served by this REST API
+    }
+    else
     {
       LOG(INFO) << "REST method " << EnumerationToString(method) 
                 << " not allowed on: " << Toolbox::FlattenUri(uri);
-      output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
+
+      output.SendMethodNotAllowedError(MethodsToString(methods));
+
+      return true;
     }
   }
 
   void RestApi::Register(const std::string& path,
                          RestApiGetCall::Handler handler)
   {
-    getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
                          RestApiPutCall::Handler handler)
   {
-    putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
                          RestApiPostCall::Handler handler)
   {
-    postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
                          RestApiDeleteCall::Handler handler)
   {
-    deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 }
--- a/Core/RestApi/RestApi.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/RestApi/RestApi.h	Thu Jul 10 11:31:14 2014 +0200
@@ -32,13 +32,7 @@
 
 #pragma once
 
-#include "../HttpServer/HttpHandler.h"
-#include "RestApiPath.h"
-#include "RestApiOutput.h"
-#include "RestApiGetCall.h"
-#include "RestApiPutCall.h"
-#include "RestApiPostCall.h"
-#include "RestApiDeleteCall.h"
+#include "RestApiHierarchy.h"
 
 #include <list>
 
@@ -47,33 +41,10 @@
   class RestApi : public HttpHandler
   {
   private:
-    typedef std::list< std::pair<RestApiPath*, RestApiGetCall::Handler> > GetHandlers;
-    typedef std::list< std::pair<RestApiPath*, RestApiPutCall::Handler> > PutHandlers;
-    typedef std::list< std::pair<RestApiPath*, RestApiPostCall::Handler> > PostHandlers;
-    typedef std::list< std::pair<RestApiPath*, RestApiDeleteCall::Handler> > DeleteHandlers;
-
-    GetHandlers  getHandlers_;
-    PutHandlers  putHandlers_;
-    PostHandlers  postHandlers_;
-    DeleteHandlers  deleteHandlers_;
-
-    bool IsGetAccepted(const UriComponents& uri);
-    bool IsPutAccepted(const UriComponents& uri);
-    bool IsPostAccepted(const UriComponents& uri);
-    bool IsDeleteAccepted(const UriComponents& uri);
-
-    std::string  GetAcceptedMethods(const UriComponents& uri);
+    RestApiHierarchy root_;
 
   public:
-    RestApi()
-    {
-    }
-
-    ~RestApi();
-
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(HttpOutput& output,
+    virtual bool Handle(HttpOutput& output,
                         HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
--- a/Core/Toolbox.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/Toolbox.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -817,5 +817,12 @@
       }
     }
   }
+
+
+  bool Toolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
 }
 
--- a/Core/Toolbox.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/Core/Toolbox.h	Thu Jul 10 11:31:14 2014 +0200
@@ -134,5 +134,7 @@
                              const std::string& source);
 
     void CreateDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
   }
 }
--- a/NEWS	Thu Jul 10 10:43:47 2014 +0200
+++ b/NEWS	Thu Jul 10 11:31:14 2014 +0200
@@ -1,13 +1,12 @@
 Pending changes in the mainline
 ===============================
 
-
 Major changes
 -------------
 
+* Introduction of the Orthanc Plugin SDK
 * Official support of OS X (Darwin)
 
-
 Minor changes
 -------------
 
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -589,10 +589,18 @@
   }
 
 
+  static bool IsColorImage(PixelFormat format)
+  {
+    return (format == PixelFormat_RGB24 ||
+            format == PixelFormat_RGBA32);
+  }
+
+
   bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target,
                                             DcmDataset& dataset,
                                             unsigned int frame,
-                                            PixelFormat format)
+                                            PixelFormat format,
+                                            bool allowColorConversion)
   {
     // TODO Special case for uncompressed images
     
@@ -602,6 +610,19 @@
       return false;
     }
 
+    // If specified, prevent the conversion between color and
+    // grayscale images
+    bool isSourceColor = IsColorImage(source.GetFormat());
+    bool isTargetColor = IsColorImage(format);
+
+    if (!allowColorConversion)
+    {
+      if (isSourceColor ^ isTargetColor)
+      {
+        return false;
+      }
+    }
+
     if (source.GetFormat() == format)
     {
       // No conversion is required, return the temporary image
--- a/OrthancServer/Internals/DicomImageDecoder.h	Thu Jul 10 10:43:47 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Thu Jul 10 11:31:14 2014 +0200
@@ -72,7 +72,8 @@
     static bool DecodeAndTruncate(ImageBuffer& target,
                                   DcmDataset& dataset,
                                   unsigned int frame,
-                                  PixelFormat format);
+                                  PixelFormat format,
+                                  bool allowColorConversion);
 
     static bool DecodePreview(ImageBuffer& target,
                               DcmDataset& dataset,
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -38,6 +38,7 @@
 #include "../../Core/Uuid.h"
 
 #include <glog/logging.h>
+#include <stdio.h>
 
 #if defined(_MSC_VER)
 #define snprintf _snprintf
--- a/OrthancServer/ParsedDicomFile.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -282,7 +282,7 @@
 
       if (cond.good())
       {
-        output.GetLowLevelOutput().Send(&buffer[0], nbytes);
+        output.GetLowLevelOutput().SendBodyData(&buffer[0], nbytes);
         offset += nbytes;
       }
       else
@@ -1172,15 +1172,15 @@
     switch (mode)
     {
       case ImageExtractionMode_UInt8:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8, false);
         break;
 
       case ImageExtractionMode_UInt16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16, false);
         break;
 
       case ImageExtractionMode_Int16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16, false);
         break;
 
       case ImageExtractionMode_Preview:
--- a/OrthancServer/main.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/OrthancServer/main.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -48,6 +48,8 @@
 #include "OrthancFindRequestHandler.h"
 #include "OrthancMoveRequestHandler.h"
 #include "ServerToolbox.h"
+#include "../Plugins/Engine/PluginsManager.h"
+#include "../Plugins/Engine/PluginsHttpHandler.h"
 
 using namespace Orthanc;
 
@@ -238,7 +240,7 @@
 };
 
 
-void PrintHelp(char* path)
+static void PrintHelp(char* path)
 {
   std::cout 
     << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
@@ -265,7 +267,7 @@
 }
 
 
-void PrintVersion(char* path)
+static void PrintVersion(char* path)
 {
   std::cout
     << path << " " << ORTHANC_VERSION << std::endl
@@ -278,6 +280,41 @@
 }
 
 
+
+static void LoadLuaScripts(ServerContext& context)
+{
+  std::list<std::string> luaScripts;
+  Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
+  for (std::list<std::string>::const_iterator
+         it = luaScripts.begin(); it != luaScripts.end(); ++it)
+  {
+    std::string path = Configuration::InterpretStringParameterAsPath(*it);
+    LOG(WARNING) << "Installing the Lua scripts from: " << path;
+    std::string script;
+    Toolbox::ReadFile(script, path);
+
+    ServerContext::LuaContextLocker locker(context);
+    locker.GetLua().Execute(script);
+  }
+}
+
+
+static void LoadPlugins(PluginsManager& pluginsManager)
+{
+  std::list<std::string> plugins;
+  Configuration::GetGlobalListOfStringsParameter(plugins, "Plugins");
+  for (std::list<std::string>::const_iterator
+         it = plugins.begin(); it != plugins.end(); ++it)
+  {
+    std::string path = Configuration::InterpretStringParameterAsPath(*it);
+    LOG(WARNING) << "Registering a plugin from: " << path;
+    pluginsManager.RegisterPlugin(path);
+  }  
+}
+
+
+
+
 int main(int argc, char* argv[]) 
 {
   // Initialize Google's logging library.
@@ -371,20 +408,7 @@
     context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
     context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
 
-    std::list<std::string> luaScripts;
-    Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
-    for (std::list<std::string>::const_iterator
-           it = luaScripts.begin(); it != luaScripts.end(); ++it)
-    {
-      std::string path = Configuration::InterpretStringParameterAsPath(*it);
-      LOG(WARNING) << "Installing the Lua scripts from: " << path;
-      std::string script;
-      Toolbox::ReadFile(script, path);
-
-      ServerContext::LuaContextLocker locker(context);
-      locker.GetLua().Execute(script);
-    }
-
+    LoadLuaScripts(context);
 
     try
     {
@@ -441,13 +465,25 @@
         httpServer.SetSslEnabled(false);
       }
 
+      OrthancRestApi restApi(context);
+
 #if ORTHANC_STANDALONE == 1
-      httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER));
+      EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER);
 #else
-      httpServer.RegisterHandler(new FilesystemHttpHandler("/app", ORTHANC_PATH "/OrthancExplorer"));
+      FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer");
 #endif
 
-      httpServer.RegisterHandler(new OrthancRestApi(context));
+      PluginsHttpHandler httpPlugins(context);
+      httpPlugins.SetOrthancRestApi(restApi);
+
+      PluginsManager pluginsManager;
+      pluginsManager.RegisterServiceProvider(httpPlugins);
+      LoadPlugins(pluginsManager);
+
+      httpServer.RegisterHandler(httpPlugins);
+      httpServer.RegisterHandler(staticResources);
+      httpServer.RegisterHandler(restApi);
+      httpPlugins.SetOrthancRestApi(restApi);
 
       // GO !!! Start the requested servers
       if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/IPluginServiceProvider.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,51 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "../OrthancCPlugin/OrthancCPlugin.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IPluginServiceProvider : boost::noncopyable
+  {
+  public:
+    virtual ~IPluginServiceProvider()
+    {
+    }
+
+    virtual bool InvokeService(_OrthancPluginService service,
+                               const void* parameters) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsHttpHandler.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,497 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "PluginsHttpHandler.h"
+
+#include "../../Core/ChunkedBuffer.h"
+#include "../../Core/OrthancException.h"
+#include "../../Core/Toolbox.h"
+#include "../../Core/HttpServer/HttpOutput.h"
+#include "../../Core/ImageFormats/PngWriter.h"
+
+#include <boost/regex.hpp> 
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+    class StringHttpOutput : public IHttpOutputStream
+    {
+    private:
+      ChunkedBuffer buffer_;
+
+    public:
+      void GetOutput(std::string& output)
+      {
+        buffer_.Flatten(output);
+      }
+
+      virtual void OnHttpStatusReceived(HttpStatus status)
+      {
+        if (status != HttpStatus_200_Ok)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+
+      virtual void Send(bool isHeader, const void* buffer, size_t length)
+      {
+        if (!isHeader)
+        {
+          buffer_.AddChunk(reinterpret_cast<const char*>(buffer), length);
+        }
+      }
+    };
+  }
+
+
+
+  struct PluginsHttpHandler::PImpl
+  {
+    typedef std::pair<boost::regex*, OrthancPluginRestCallback> Callback;
+    typedef std::list<Callback>  Callbacks;
+
+    ServerContext& context_;
+    Callbacks callbacks_;
+    OrthancRestApi* restApi_;
+
+    PImpl(ServerContext& context) : context_(context), restApi_(NULL)
+    {
+    }
+  };
+
+
+  PluginsHttpHandler::PluginsHttpHandler(ServerContext& context)
+  {
+    pimpl_.reset(new PImpl(context));
+  }
+
+  
+  PluginsHttpHandler::~PluginsHttpHandler()
+  {
+    for (PImpl::Callbacks::iterator it = pimpl_->callbacks_.begin(); 
+         it != pimpl_->callbacks_.end(); ++it)
+    {
+      delete it->first;
+    }
+  }
+
+
+  bool PluginsHttpHandler::Handle(HttpOutput& output,
+                                  HttpMethod method,
+                                  const UriComponents& uri,
+                                  const Arguments& headers,
+                                  const Arguments& getArguments,
+                                  const std::string& postData)
+  {
+    std::string flatUri = Toolbox::FlattenUri(uri);
+    OrthancPluginRestCallback callback = NULL;
+
+    std::vector<std::string> groups;
+    std::vector<const char*> cgroups;
+
+    bool found = false;
+    for (PImpl::Callbacks::const_iterator it = pimpl_->callbacks_.begin(); 
+         it != pimpl_->callbacks_.end() && !found; ++it)
+    {
+      boost::cmatch what;
+      if (boost::regex_match(flatUri.c_str(), what, *(it->first)))
+      {
+        callback = it->second;
+
+        if (what.size() > 1)
+        {
+          groups.resize(what.size() - 1);
+          cgroups.resize(what.size() - 1);
+          for (size_t i = 1; i < what.size(); i++)
+          {
+            groups[i - 1] = what[i];
+            cgroups[i - 1] = groups[i - 1].c_str();
+          }
+        }
+
+        found = true;
+      }
+    }
+
+    if (!found)
+    {
+      return false;
+    }
+
+    LOG(INFO) << "Delegating HTTP request to plugin for URI: " << flatUri;
+
+    std::vector<const char*> getKeys(getArguments.size());
+    std::vector<const char*> getValues(getArguments.size());
+
+    OrthancPluginHttpRequest request;
+    memset(&request, 0, sizeof(OrthancPluginHttpRequest));
+
+    switch (method)
+    {
+      case HttpMethod_Get:
+      {
+        request.method = OrthancPluginHttpMethod_Get;
+
+        size_t i = 0;
+        for (Arguments::const_iterator it = getArguments.begin(); 
+             it != getArguments.end(); ++it, ++i)
+        {
+          getKeys[i] = it->first.c_str();
+          getValues[i] = it->second.c_str();
+        }
+
+        break;
+      }
+
+      case HttpMethod_Post:
+        request.method = OrthancPluginHttpMethod_Post;
+        break;
+
+      case HttpMethod_Delete:
+        request.method = OrthancPluginHttpMethod_Delete;
+        break;
+
+      case HttpMethod_Put:
+        request.method = OrthancPluginHttpMethod_Put;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+
+    request.groups = (cgroups.size() ? &cgroups[0] : NULL);
+    request.groupsCount = cgroups.size();
+    request.getCount = getArguments.size();
+    request.body = (postData.size() ? &postData[0] : NULL);
+    request.bodySize = postData.size();
+
+    if (getArguments.size() > 0)
+    {
+      request.getKeys = &getKeys[0];
+      request.getValues = &getValues[0];
+    }
+
+    assert(callback != NULL);
+    int32_t error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), 
+                             flatUri.c_str(), 
+                             &request);
+
+    if (error < 0)
+    {
+      LOG(ERROR) << "Plugin failed with error code " << error;
+      return false;
+    }
+    else
+    {
+      if (error > 0)
+      {
+        LOG(WARNING) << "Plugin finished with warning code " << error;
+      }
+
+      return true;
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const void* data,
+                                 size_t size)
+  {
+    target.size = size;
+
+    if (size == 0)
+    {
+      target.data = NULL;
+    }
+    else
+    {
+      target.data = malloc(size);
+      if (target.data != NULL)
+      {
+        memcpy(target.data, data, size);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const std::string& str)
+  {
+    if (str.size() == 0)
+    {
+      target.size = 0;
+      target.data = NULL;
+    }
+    else
+    {
+      CopyToMemoryBuffer(target, str.c_str(), str.size());
+    }
+  }
+
+
+  void PluginsHttpHandler::RegisterRestCallback(const void* parameters)
+  {
+    const _OrthancPluginRestCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a REST callback on: " << p.pathRegularExpression;
+    pimpl_->callbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback));
+  }
+
+
+
+  void PluginsHttpHandler::AnswerBuffer(const void* parameters)
+  {
+    const _OrthancPluginAnswerBuffer& p = 
+      *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    translatedOutput->AnswerBufferWithContentType(p.answer, p.answerSize, p.mimeType);
+  }
+
+
+  void PluginsHttpHandler::Redirect(const void* parameters)
+  {
+    const _OrthancPluginRedirect& p = 
+      *reinterpret_cast<const _OrthancPluginRedirect*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    translatedOutput->Redirect(p.redirection);
+  }
+
+
+  void PluginsHttpHandler::CompressAndAnswerPngImage(const void* parameters)
+  {
+    const _OrthancPluginCompressAndAnswerPngImage& p = 
+      *reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+
+    PixelFormat format;
+    switch (p.format)
+    {
+      case OrthancPluginPixelFormat_Grayscale8:  
+        format = PixelFormat_Grayscale8;
+        break;
+
+      case OrthancPluginPixelFormat_Grayscale16:  
+        format = PixelFormat_Grayscale16;
+        break;
+
+      case OrthancPluginPixelFormat_SignedGrayscale16:  
+        format = PixelFormat_SignedGrayscale16;
+        break;
+
+      case OrthancPluginPixelFormat_RGB24:  
+        format = PixelFormat_RGB24;
+        break;
+
+      case OrthancPluginPixelFormat_RGBA32:  
+        format = PixelFormat_RGBA32;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(format, p.width, p.height, p.pitch, p.buffer);
+
+    PngWriter writer;
+    std::string png;
+    writer.WriteToMemory(png, accessor);
+
+    translatedOutput->AnswerBufferWithContentType(png, "image/png");
+  }
+
+
+  void PluginsHttpHandler::GetDicomForInstance(const void* parameters)
+  {
+    const _OrthancPluginGetDicomForInstance& p = 
+      *reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
+
+    std::string dicom;
+    pimpl_->context_.ReadFile(dicom, p.instanceId, FileContentType_Dicom);
+    CopyToMemoryBuffer(*p.target, dicom);
+  }
+
+
+  void PluginsHttpHandler::RestApiGet(const void* parameters)
+  {
+    const _OrthancPluginRestApiGet& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
+        
+    HttpHandler::Arguments headers;  // No HTTP header
+    std::string body;  // No body for a GET request
+
+    UriComponents uri;
+    HttpHandler::Arguments getArguments;
+    HttpHandler::ParseGetQuery(uri, getArguments, p.uri);
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri;
+
+    if (pimpl_->restApi_ != NULL &&
+        pimpl_->restApi_->Handle(http, HttpMethod_Get, uri, headers, getArguments, body))
+    {
+      std::string result;
+      stream.GetOutput(result);
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  void PluginsHttpHandler::RestApiPostPut(bool isPost, const void* parameters)
+  {
+    const _OrthancPluginRestApiPostPut& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
+
+    HttpHandler::Arguments headers;  // No HTTP header
+    HttpHandler::Arguments getArguments;  // No GET argument for POST/PUT
+
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, p.uri);
+
+    // TODO Avoid unecessary memcpy
+    std::string body(p.body, p.bodySize);
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    HttpMethod method = (isPost ? HttpMethod_Post : HttpMethod_Put);
+    LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri;
+
+    if (pimpl_->restApi_ != NULL &&
+        pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, body))
+    {
+      std::string result;
+      stream.GetOutput(result);
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  void PluginsHttpHandler::RestApiDelete(const void* parameters)
+  {
+    // The "parameters" point to the URI
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, reinterpret_cast<const char*>(parameters));
+
+    HttpHandler::Arguments headers;  // No HTTP header
+    HttpHandler::Arguments getArguments;  // No GET argument for POST/PUT
+    std::string body;  // No body for DELETE
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    LOG(INFO) << "Plugin making REST DELETE call on URI " 
+              << reinterpret_cast<const char*>(parameters);
+
+    if (pimpl_->restApi_ == NULL ||
+        !pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, body))
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  bool PluginsHttpHandler::InvokeService(_OrthancPluginService service,
+                                         const void* parameters)
+  {
+    switch (service)
+    {
+      case _OrthancPluginService_RegisterRestCallback:
+        RegisterRestCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_AnswerBuffer:
+        AnswerBuffer(parameters);
+        return true;
+
+      case _OrthancPluginService_CompressAndAnswerPngImage:
+        CompressAndAnswerPngImage(parameters);
+        return true;
+
+      case _OrthancPluginService_GetDicomForInstance:
+        GetDicomForInstance(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiGet:
+        RestApiGet(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiPost:
+        RestApiPostPut(true, parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiDelete:
+        RestApiDelete(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiPut:
+        RestApiPostPut(false, parameters);
+        return true;
+
+      case _OrthancPluginService_Redirect:
+        Redirect(parameters);
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+
+  void PluginsHttpHandler::SetOrthancRestApi(OrthancRestApi& restApi)
+  {
+    pimpl_->restApi_ = &restApi;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsHttpHandler.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "PluginsManager.h"
+#include "../../Core/HttpServer/HttpHandler.h"
+#include "../../OrthancServer/ServerContext.h"
+#include "../../OrthancServer/OrthancRestApi/OrthancRestApi.h"
+#include "../OrthancCPlugin/OrthancCPlugin.h"
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class PluginsHttpHandler : public HttpHandler, public IPluginServiceProvider
+  {
+  private:
+    struct PImpl;
+
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void RegisterRestCallback(const void* parameters);
+
+    void AnswerBuffer(const void* parameters);
+
+    void Redirect(const void* parameters);
+
+    void CompressAndAnswerPngImage(const void* parameters);
+
+    void GetDicomForInstance(const void* parameters);
+
+    void RestApiGet(const void* parameters);
+
+    void RestApiPostPut(bool isPost, const void* parameters);
+
+    void RestApiDelete(const void* parameters);
+
+  public:
+    PluginsHttpHandler(ServerContext& context);
+
+    virtual ~PluginsHttpHandler();
+
+    virtual bool Handle(HttpOutput& output,
+                        HttpMethod method,
+                        const UriComponents& uri,
+                        const Arguments& headers,
+                        const Arguments& getArguments,
+                        const std::string& postData);
+
+    virtual bool InvokeService(_OrthancPluginService service,
+                               const void* parameters);
+
+    void SetOrthancRestApi(OrthancRestApi& restApi);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsManager.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,299 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "PluginsManager.h"
+
+#include "../../Core/Toolbox.h"
+#include "../../Core/HttpServer/HttpOutput.h"
+
+#include <glog/logging.h>
+#include <cassert>
+#include <memory>
+#include <boost/filesystem.hpp>
+
+#ifdef WIN32
+#define PLUGIN_EXTENSION ".dll"
+#elif defined(__linux)
+#define PLUGIN_EXTENSION ".so"
+#else
+#error Support your platform here
+#endif
+
+
+namespace Orthanc
+{
+  static void CallInitialize(SharedLibrary& plugin,
+                             const OrthancPluginContext& context)
+  {
+    typedef int32_t (*Initialize) (const OrthancPluginContext*);
+
+#if defined(_WIN32)
+    Initialize initialize = (Initialize) plugin.GetFunction("OrthancPluginInitialize");
+#else
+    /**
+     * gcc would complain about "ISO C++ forbids casting between
+     * pointer-to-function and pointer-to-object" without the trick
+     * below, that is known as "the POSIX.1-2003 (Technical Corrigendum
+     * 1) workaround". See the man page of "dlsym()".
+     * http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
+     * http://stackoverflow.com/a/14543811/881731
+     **/
+
+    Initialize initialize;
+    *(void **) (&initialize) = plugin.GetFunction("OrthancPluginInitialize");
+#endif
+
+    assert(initialize != NULL);
+    int32_t error = initialize(&context);
+
+    if (error != 0)
+    {
+      LOG(ERROR) << "Error while initializing plugin " << plugin.GetPath()
+                 << " (code " << error << ")";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+  }
+
+
+  static void CallFinalize(SharedLibrary& plugin)
+  {
+    typedef void (*Finalize) ();
+
+#if defined(_WIN32)
+    Finalize finalize = (Finalize) plugin.GetFunction("OrthancPluginFinalize");
+#else
+    Finalize finalize;
+    *(void **) (&finalize) = plugin.GetFunction("OrthancPluginFinalize");
+#endif
+
+    assert(finalize != NULL);
+    finalize();
+  }
+
+
+  static const char* CallGetName(SharedLibrary& plugin)
+  {
+    typedef const char* (*GetName) ();
+
+#if defined(_WIN32)
+    GetName getName = (GetName) plugin.GetFunction("OrthancPluginGetName");
+#else
+    GetName getName;
+    *(void **) (&getName) = plugin.GetFunction("OrthancPluginGetName");
+#endif
+
+    assert(getName != NULL);
+    return getName();
+  }
+
+
+  static const char* CallGetVersion(SharedLibrary& plugin)
+  {
+    typedef const char* (*GetVersion) ();
+
+#if defined(_WIN32)
+    GetVersion getVersion = (GetVersion) plugin.GetFunction("OrthancPluginGetVersion");
+#else
+    GetVersion getVersion;
+    *(void **) (&getVersion) = plugin.GetFunction("OrthancPluginGetVersion");
+#endif
+
+    assert(getVersion != NULL);
+    return getVersion();
+  }
+
+
+  int32_t PluginsManager::InvokeService(OrthancPluginContext* context,
+                                        _OrthancPluginService service, 
+                                        const void* params)
+  {
+    switch (service)
+    {
+      case _OrthancPluginService_LogError:
+        LOG(ERROR) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      case _OrthancPluginService_LogWarning:
+        LOG(WARNING) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      case _OrthancPluginService_LogInfo:
+        LOG(INFO) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      default:
+        break;
+    }
+
+    PluginsManager* that = reinterpret_cast<PluginsManager*>(context->pluginsManager);
+    bool error = false;
+
+    for (std::list<IPluginServiceProvider*>::iterator
+           it = that->serviceProviders_.begin(); 
+         it != that->serviceProviders_.end(); ++it)
+    {
+      try
+      {
+        if ((*it)->InvokeService(service, params))
+        {
+          return 0;
+        }
+      }
+      catch (OrthancException&)
+      {
+        // This service provider has failed, go to the next
+        error = true;
+      }
+    }
+
+    if (error)
+    {
+      LOG(ERROR) << "Exception when dealing with service " << service;
+    }
+    else
+    {
+      LOG(ERROR) << "Plugin invoking unknown service " << service;
+    }
+
+    return -1;
+  }
+
+
+  PluginsManager::PluginsManager()
+  {
+    memset(&context_, 0, sizeof(context_));
+    context_.pluginsManager = this;
+    context_.orthancVersion = ORTHANC_VERSION;
+    context_.Free = ::free;
+    context_.InvokeService = InvokeService;
+  }
+
+  PluginsManager::~PluginsManager()
+  {
+    for (Plugins::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        LOG(WARNING) << "Unregistering plugin '" << CallGetName(*it->second)
+                     << "' (version " << CallGetVersion(*it->second) << ")";
+
+        CallFinalize(*(it->second));
+        delete it->second;
+      }
+    }
+  }
+
+
+  static bool IsOrthancPlugin(SharedLibrary& library)
+  {
+    return (library.HasFunction("OrthancPluginInitialize") &&
+            library.HasFunction("OrthancPluginFinalize") &&
+            library.HasFunction("OrthancPluginGetName") &&
+            library.HasFunction("OrthancPluginGetVersion"));
+  }
+
+  
+  void PluginsManager::RegisterPlugin(const std::string& path)
+  {
+    std::auto_ptr<SharedLibrary> plugin(new SharedLibrary(path));
+
+    if (!IsOrthancPlugin(*plugin))
+    {
+      LOG(ERROR) << "Plugin " << plugin->GetPath()
+                 << " does not declare the proper entry functions";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+    std::string name(CallGetName(*plugin));
+    if (plugins_.find(name) != plugins_.end())
+    {
+      LOG(ERROR) << "Plugin '" << name << "' already registered";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+    LOG(WARNING) << "Registering plugin '" << name
+                 << "' (version " << CallGetVersion(*plugin) << ")";
+
+    CallInitialize(*plugin, context_);
+
+    plugins_[name] = plugin.release();
+  }
+
+
+  void PluginsManager::ScanFolderForPlugins(const std::string& folder,
+                                            bool isRecursive)
+  {
+    using namespace boost::filesystem;
+
+    if (!exists(folder))
+    {
+      return;
+    }
+
+    LOG(INFO) << "Scanning folder " << folder << " for plugins";
+
+    directory_iterator end_it; // default construction yields past-the-end
+    for (directory_iterator it(folder);
+          it != end_it;
+          ++it)
+    {
+      std::string path = it->path().string();
+
+      if (is_directory(it->status()))
+      {
+        if (isRecursive)
+        {
+          ScanFolderForPlugins(path, true);
+        }
+      }
+      else
+      {
+        if (boost::filesystem::extension(it->path()) == PLUGIN_EXTENSION)
+        {
+          LOG(INFO) << "Found a shared library: " << it->path();
+
+          try
+          {
+            SharedLibrary plugin(path);
+            if (IsOrthancPlugin(plugin))
+            {
+              RegisterPlugin(path);
+            }
+          }
+          catch (OrthancException&)
+          {
+          }
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsManager.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "SharedLibrary.h"
+#include "IPluginServiceProvider.h"
+
+#include <map>
+#include <list>
+
+namespace Orthanc
+{
+  class PluginsManager : boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, SharedLibrary*>  Plugins;
+
+    OrthancPluginContext  context_;
+    Plugins  plugins_;
+    std::list<IPluginServiceProvider*> serviceProviders_;
+
+    static int32_t InvokeService(OrthancPluginContext* context,
+                                 _OrthancPluginService service,
+                                 const void* parameters);
+
+  public:
+    PluginsManager();
+
+    ~PluginsManager();
+
+    void RegisterPlugin(const std::string& path);
+
+    void ScanFolderForPlugins(const std::string& path,
+                              bool isRecursive);
+
+    void RegisterServiceProvider(IPluginServiceProvider& provider)
+    {
+      serviceProviders_.push_back(&provider);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/SharedLibrary.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,133 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "SharedLibrary.h"
+
+#include "../../Core/Toolbox.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__linux)
+#include <dlfcn.h>
+#else
+#error Support your platform here
+#endif
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  SharedLibrary::SharedLibrary(const std::string& path) : 
+    path_(path),
+    handle_(NULL)
+  {
+#if defined(_WIN32)
+    handle_ = ::LoadLibraryA(path.c_str());
+    if (handle_ == NULL)
+    {
+      LOG(ERROR) << "LoadLibrary(" << path << ") failed: Error " << ::GetLastError();
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#elif defined(__linux)
+    handle_ = ::dlopen(path.c_str(), RTLD_NOW);
+    if (handle_ == NULL) 
+    {
+      std::string explanation;
+      const char *tmp = ::dlerror();
+      if (tmp)
+      {
+        explanation = ": Error " + std::string(tmp);
+      }
+
+      LOG(ERROR) << "dlopen(" << path << ") failed" << explanation;
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#else
+#error Support your platform here
+#endif   
+  }
+
+  SharedLibrary::~SharedLibrary()
+  {
+    if (handle_)
+    {
+#if defined(_WIN32)
+      ::FreeLibrary((HMODULE)handle_);
+#elif defined(__linux)
+      ::dlclose(handle_);
+#else
+#error Support your platform here
+#endif
+    }
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name)
+  {
+    if (!handle_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+#if defined(_WIN32)
+    return ::GetProcAddress((HMODULE)handle_, name.c_str());
+#elif defined(__linux)
+    return ::dlsym(handle_, name.c_str());
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name)
+  {
+    SharedLibrary::FunctionPointer result = GetFunctionInternal(name);
+  
+    if (result == NULL)
+    {
+      LOG(ERROR) << "Shared library does not expose function \"" << name << "\"";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  bool SharedLibrary::HasFunction(const std::string& name)
+  {
+    return GetFunctionInternal(name) != NULL;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/SharedLibrary.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "../../Core/OrthancException.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class SharedLibrary : boost::noncopyable
+  {
+  public:
+#if defined(_WIN32)
+    typedef FARPROC FunctionPointer;
+#else
+    typedef void* FunctionPointer;
+#endif
+
+  private:
+    std::string path_;
+    void *handle_;
+
+    FunctionPointer GetFunctionInternal(const std::string& name);
+
+  public:
+    SharedLibrary(const std::string& path);
+
+    ~SharedLibrary();
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool HasFunction(const std::string& name);
+
+    FunctionPointer GetFunction(const std::string& name);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,702 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must store the context pointer so that it can use the plugin 
+ *    services of Orthanc. It must also register all its callbacks using
+ *    ::OrthancPluginRegisterRestCallback().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice.
+ * 
+ * 
+ **/
+
+
+
+/**
+ * @defgroup CInterface C Interface 
+ * @brief The C interface to create Orthanc plugins.
+ * 
+ * These functions must be used to create C plugins for Orthanc.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+#ifdef _MSC_VER
+#include "../../Resources/VisualStudio/stdint.h"
+#else
+#include <stdint.h>
+#endif
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4  /*!< DELETE request */
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+  } OrthancPluginHttpRequest;
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,
+    _OrthancPluginService_Redirect = 2002,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004
+  } _OrthancPluginService;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5
+  } OrthancPluginPixelFormat;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   **/
+  typedef int32_t (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+  /**
+   * @brief Opaque structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*        pluginsManager;
+    const char*  orthancVersion;
+    void       (*Free) (void* buffer);
+    int32_t    (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                 _OrthancPluginService service,
+                                 const void* params);
+  } OrthancPluginContext;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    context->Free(str);
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer.
+   * @param buffer The memory buffer containing the uncompressed image.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerPngImage params;
+    params.output = output;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerPngImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              redirection;
+  } _OrthancPluginRedirect;
+
+  /**
+   * @brief Redirect a GET request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginRedirect params;
+    params.output = output;
+    params.redirection = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/Basic/CMakeLists.txt	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(Basic)
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Werror")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror")
+endif()
+
+include_directories(${CMAKE_SOURCE_DIR}/../../OrthancCPlugin/)
+add_library(PluginTest SHARED Plugin.c)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  # Linking with "pthread" is necessary, otherwise the software crashes
+  # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17
+  target_link_libraries(PluginTest pthread dl)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/Basic/Plugin.c	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,195 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <OrthancCPlugin.h>
+
+#include <string.h>
+#include <stdio.h>
+
+static OrthancPluginContext* context = NULL;
+
+
+ORTHANC_PLUGINS_API int32_t Callback1(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  char buffer[1024];
+  uint32_t i;
+
+  sprintf(buffer, "Callback on URL [%s] with body [%s]", url, request->body);
+  OrthancPluginLogInfo(context, buffer);
+
+  OrthancPluginAnswerBuffer(context, output, buffer, strlen(buffer), "text/plain");
+
+  for (i = 0; i < request->getCount; i++)
+  {
+    sprintf(buffer, "  [%s] = [%s]", request->getKeys[i], request->getValues[i]);
+    OrthancPluginLogInfo(context, buffer);    
+  }
+
+  printf("** %d\n", request->groupsCount);
+  for (i = 0; i < request->groupsCount; i++)
+  {
+    printf("** [%s]\n",  request->groups[i]);
+  }
+
+  return 1;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback2(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  /* Answer with a sample 16bpp image. */
+
+  uint16_t buffer[256 * 256];
+  uint32_t x, y, value;
+
+  value = 0;
+  for (y = 0; y < 256; y++)
+  {
+    for (x = 0; x < 256; x++, value++)
+    {
+      buffer[value] = value;
+    }
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale16,
+                                         256, 256, sizeof(uint16_t) * 256, buffer);
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback3(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginMemoryBuffer dicom;
+  if (!OrthancPluginGetDicomForInstance(context, &dicom, request->groups[0]))
+  {
+    /* No error, forward the DICOM file */
+    OrthancPluginAnswerBuffer(context, output, dicom.data, dicom.size, "application/dicom");
+
+    /* Free memory */
+    OrthancPluginFreeMemoryBuffer(context, &dicom);
+  }
+
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback4(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  /* Answer with a sample 8bpp image. */
+
+  uint8_t  buffer[256 * 256];
+  uint32_t x, y, value;
+
+  value = 0;
+  for (y = 0; y < 256; y++)
+  {
+    for (x = 0; x < 256; x++, value++)
+    {
+      buffer[value] = x;
+    }
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale8,
+                                         256, 256, 256, buffer);
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+{
+  const char* pathLocator = "\"Path\" : \"";
+  OrthancPluginMemoryBuffer tmp;
+  char info[1024];
+  char *id, *eos;
+
+  context = c;
+  OrthancPluginLogWarning(context, "Sample plugin is initializing");
+
+  sprintf(info, "The version of Orthanc is '%s'", context->orthancVersion);
+  OrthancPluginLogInfo(context, info);
+
+  OrthancPluginRegisterRestCallback(context, "/(plu.*)/hello", Callback1);
+  OrthancPluginRegisterRestCallback(context, "/plu.*/image", Callback2);
+  OrthancPluginRegisterRestCallback(context, "/plugin/instances/([^/]+)/info", Callback3);
+
+  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
+
+  /* Make REST requests to the built-in Orthanc API */
+  OrthancPluginRestApiGet(context, &tmp, "/changes");
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+  OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1");
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+ 
+  /* Make POST request to create a new DICOM instance */
+  sprintf(info, "{\"PatientName\":\"Test\"}");
+  OrthancPluginRestApiPost(context, &tmp, "/tools/create-dicom", info, strlen(info));
+
+  /**
+   * Recover he ID of the created instance is constructed by a
+   * quick-and-dirty parsing of a JSON string.
+   **/
+  id = strstr((char*) tmp.data, pathLocator) + strlen(pathLocator);
+  eos = strchr(id, '\"');
+  eos[0] = '\0';
+
+  /* Delete the newly created DICOM instance. */
+  OrthancPluginRestApiDelete(context, id);
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+
+  /* Play with PUT by defining a new target modality. */
+  sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
+  OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
+
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+{
+  OrthancPluginLogWarning(context, "Sample plugin is finalizing");
+}
+
+
+ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+{
+  return "sample";
+}
+
+
+ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+{
+  return "1.0";
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,56 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(GdcmDecoding)
+
+SET(ALLOW_DOWNLOADS ON CACHE BOOL "Allow CMake to download packages")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_GOOGLE_LOG OFF CACHE BOOL "Use the system version of Google Log")
+
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..)
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+endif()
+
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake)
+
+find_package(GDCM REQUIRED)
+if (GDCM_FOUND)
+  include(${GDCM_USE_FILE})
+  set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
+else(GDCM_FOUND)
+  message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
+endif(GDCM_FOUND)
+
+include_directories(
+  ${ORTHANC_ROOT}/Plugins/OrthancCPlugin/
+  ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw
+  )
+
+add_library(GdcmDecoding SHARED
+  Plugin.cpp
+  OrthancContext.cpp
+
+  # Sources from Orthanc
+  ${GOOGLE_LOG_SOURCES}
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/OrthancException.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c
+  ${THIRD_PARTY_SOURCES}
+  )
+
+target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES})
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  target_link_libraries(GdcmDecoding pthread dl rt)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,167 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include "OrthancContext.h"
+
+#include <stdexcept>
+
+
+void OrthancContext::Check()
+{
+  if (context_ == NULL)
+  {
+    throw std::runtime_error("The Orthanc plugin context is not initialized");
+  }
+}
+
+
+OrthancContext& OrthancContext::GetInstance()
+{
+  static OrthancContext instance;
+  return instance;
+}
+
+
+OrthancContext::~OrthancContext()
+{
+  if (context_ != NULL)
+  {
+    throw std::runtime_error("The Orthanc plugin was not properly finalized");
+  }
+}
+
+
+void OrthancContext::ExtractGetArguments(Arguments& arguments,
+                                         const OrthancPluginHttpRequest& request)
+{
+  Check();
+  arguments.clear();
+
+  for (uint32_t i = 0; i < request.getCount; i++)
+  {
+    arguments[request.getKeys[i]] = request.getValues[i];
+  }
+}
+
+
+void OrthancContext::LogError(const std::string& s)
+{
+  Check();
+  OrthancPluginLogError(context_, s.c_str());
+}
+
+
+void OrthancContext::LogWarning(const std::string& s)
+{
+  Check();
+  OrthancPluginLogWarning(context_, s.c_str());
+}
+
+
+void OrthancContext::LogInfo(const std::string& s)
+{
+  Check();
+  OrthancPluginLogInfo(context_, s.c_str());
+}
+  
+
+void OrthancContext::Register(const std::string& uri,
+                              OrthancPluginRestCallback callback)
+{
+  Check();
+  OrthancPluginRegisterRestCallback(context_, uri.c_str(), callback);
+}
+
+
+void OrthancContext::GetDicomForInstance(std::string& result,
+                                         const std::string& instanceId)
+{
+  Check();
+  OrthancPluginMemoryBuffer buffer;
+    
+  if (OrthancPluginGetDicomForInstance(context_, &buffer, instanceId.c_str()))
+  {
+    throw std::runtime_error("No DICOM instance with Orthanc ID: " + instanceId);
+  }
+
+  if (buffer.size == 0)
+  {
+    result.clear();
+  }
+  else
+  {
+    result.assign(reinterpret_cast<char*>(buffer.data), buffer.size);
+  }
+
+  OrthancPluginFreeMemoryBuffer(context_, &buffer);
+}
+
+
+void OrthancContext::CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
+                                               const Orthanc::ImageAccessor& accessor)
+{
+  Check();
+
+  OrthancPluginPixelFormat format;
+  switch (accessor.GetFormat())
+  {
+    case Orthanc::PixelFormat_Grayscale8:
+      format = OrthancPluginPixelFormat_Grayscale8;
+      break;
+
+    case Orthanc::PixelFormat_Grayscale16:
+      format = OrthancPluginPixelFormat_Grayscale16;
+      break;
+
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      format = OrthancPluginPixelFormat_SignedGrayscale16;
+      break;
+
+    case Orthanc::PixelFormat_RGB24:
+      format = OrthancPluginPixelFormat_RGB24;
+      break;
+
+    case Orthanc::PixelFormat_RGBA32:
+      format = OrthancPluginPixelFormat_RGBA32;
+      break;
+
+    default:
+      throw std::runtime_error("Unsupported pixel format");
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context_, output, format, accessor.GetWidth(),
+                                         accessor.GetHeight(), accessor.GetPitch(), accessor.GetConstBuffer());
+}
+
+
+
+void OrthancContext::Redirect(OrthancPluginRestOutput* output,
+                              const std::string& s)
+{
+  Check();
+  OrthancPluginRedirect(context_, output, s.c_str());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.h	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,87 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#pragma once
+
+#include <OrthancCPlugin.h>
+
+#include "../../../Core/ImageFormats/ImageBuffer.h"
+
+#include <map>
+#include <string>
+#include <boost/noncopyable.hpp>
+
+
+class OrthancContext : public boost::noncopyable
+{
+private:
+  OrthancPluginContext* context_;
+
+  OrthancContext() : context_(NULL)
+  {
+  }
+
+  void Check();
+
+public:
+  typedef std::map<std::string, std::string>  Arguments;
+
+  static OrthancContext& GetInstance();
+
+  ~OrthancContext();
+
+  void Initialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+  }
+
+  void Finalize()
+  {
+    context_ = NULL;
+  }
+
+  void ExtractGetArguments(Arguments& arguments,
+                           const OrthancPluginHttpRequest& request);
+
+  void LogError(const std::string& s);
+
+  void LogWarning(const std::string& s);
+
+  void LogInfo(const std::string& s);
+  
+  void Register(const std::string& uri,
+                OrthancPluginRestCallback callback);
+
+  void GetDicomForInstance(std::string& result,
+                           const std::string& instanceId);
+
+  void CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
+                                 const Orthanc::ImageAccessor& accessor);
+
+  void Redirect(OrthancPluginRestOutput* output,
+                const std::string& s);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/Plugin.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,257 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <map>
+#include <string>
+#include <stdexcept>
+#include <sstream>
+
+#include "OrthancContext.h"
+#include "../../../Core/ImageFormats/ImageProcessing.h"
+
+#include <gdcmReader.h>
+#include <gdcmImageReader.h>
+#include <gdcmImageChangePlanarConfiguration.h>
+
+
+static void AnswerUnsupportedImage(OrthancPluginRestOutput* output)
+{
+  OrthancContext::GetInstance().Redirect(output, "/app/images/unsupported.png");
+}
+
+
+static bool GetOrthancPixelFormat(Orthanc::PixelFormat& format,
+                                  const gdcm::Image& image)
+{
+  if (image.GetPlanarConfiguration() != 0 && 
+      image.GetPixelFormat().GetSamplesPerPixel() != 1)
+  {
+    OrthancContext::GetInstance().LogError("Planar configurations are not supported");
+    return false;
+  }
+
+  if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
+  {
+    switch (image.GetPixelFormat().GetScalarType())
+    {
+      case gdcm::PixelFormat::UINT8:
+        format = Orthanc::PixelFormat_Grayscale8;
+        return true;
+
+      case gdcm::PixelFormat::UINT16:
+        format = Orthanc::PixelFormat_Grayscale16;
+        return true;
+
+      case gdcm::PixelFormat::INT16:
+        format = Orthanc::PixelFormat_SignedGrayscale16;
+        return true;
+
+      default:
+        return false;
+    }
+  }
+  else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
+  {
+    format = Orthanc::PixelFormat_RGB24;
+    return true;
+  }
+  else if (image.GetPixelFormat().GetSamplesPerPixel() == 4 &&
+           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
+  {
+    format = Orthanc::PixelFormat_RGBA32;
+    return true;
+  }
+  
+  return false;
+}
+
+
+ORTHANC_PLUGINS_API int32_t DecodeImage(OrthancPluginRestOutput* output,
+                                        const char* url,
+                                        const OrthancPluginHttpRequest* request)
+{
+  std::string instance(request->groups[0]);
+  std::string outputFormat(request->groups[1]);
+  OrthancContext::GetInstance().LogWarning("Using GDCM to decode instance " + instance);
+
+  // Download the request DICOM instance from Orthanc into a memory buffer
+  std::string dicom;
+  OrthancContext::GetInstance().GetDicomForInstance(dicom, instance);
+
+  // Prepare a memory stream over the DICOM instance
+  std::stringstream stream(dicom);
+
+  // Parse the DICOM instance using GDCM
+  gdcm::ImageReader imageReader;
+  imageReader.SetStream(stream);
+  if (!imageReader.Read())
+  {
+    OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance");
+    AnswerUnsupportedImage(output);
+    return 0;
+  }
+
+  gdcm::Image& image = imageReader.GetImage();
+
+
+  // Log information about the decoded image
+  char tmp[1024];
+  sprintf(tmp, "Image format: %dx%d %s with %d color channel(s)", image.GetRows(), image.GetColumns(), 
+          image.GetPixelFormat().GetScalarTypeAsString(), image.GetPixelFormat().GetSamplesPerPixel());
+  OrthancContext::GetInstance().LogWarning(tmp);
+
+
+  // Convert planar configuration
+  gdcm::ImageChangePlanarConfiguration planar;
+  if (image.GetPlanarConfiguration() != 0 && 
+      image.GetPixelFormat().GetSamplesPerPixel() != 1)
+  {
+    OrthancContext::GetInstance().LogWarning("Converting planar configuration to interleaved");
+    planar.SetInput(imageReader.GetImage());
+    planar.Change();
+    image = planar.GetOutput();
+  }
+
+
+  // Create a read-only accessor to the bitmap decoded by GDCM
+  Orthanc::PixelFormat format;
+  if (!GetOrthancPixelFormat(format, image))
+  {
+    OrthancContext::GetInstance().LogError("This sample plugin does not support this image format");
+    AnswerUnsupportedImage(output);
+    return 0;
+  }
+
+  Orthanc::ImageAccessor decodedImage;
+  std::vector<char> decodedBuffer(image.GetBufferLength());
+
+  if (decodedBuffer.size())
+  {
+    image.GetBuffer(&decodedBuffer[0]);
+    unsigned int pitch = image.GetColumns() * ::Orthanc::GetBytesPerPixel(format);
+    decodedImage.AssignWritable(format, image.GetColumns(), image.GetRows(), pitch, &decodedBuffer[0]);
+  }
+  else
+  {
+    // Empty image
+    decodedImage.AssignWritable(format, 0, 0, 0, NULL);
+  }
+
+
+  // Convert the pixel format from GDCM to the format requested by the REST query
+  Orthanc::ImageBuffer converted;
+  converted.SetWidth(decodedImage.GetWidth());
+  converted.SetHeight(decodedImage.GetHeight());
+
+  if (outputFormat == "preview")
+  {
+    if (format == Orthanc::PixelFormat_RGB24 ||
+        format == Orthanc::PixelFormat_RGBA32)
+    {
+      // Do not rescale color image
+      converted.SetFormat(Orthanc::PixelFormat_RGB24);
+    }
+    else
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
+
+      // Rescale the image to the [0,255] range
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, decodedImage);
+
+      float offset = -a;
+      float scaling = 255.0f / static_cast<float>(b - a);
+      Orthanc::ImageProcessing::ShiftScale(decodedImage, offset, scaling);
+    }
+  }
+  else
+  {
+    if (format == Orthanc::PixelFormat_RGB24 ||
+        format == Orthanc::PixelFormat_RGBA32)
+    {
+      // Do not convert color images to grayscale values (this is Orthanc convention)
+      AnswerUnsupportedImage(output);
+      return 0;
+    }
+
+    if (outputFormat == "image-uint8")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
+    }
+    else if (outputFormat == "image-uint16")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale16);
+    }
+    else if (outputFormat == "image-int16")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+    }
+    else
+    {
+      OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat);
+      AnswerUnsupportedImage(output);
+      return 0;
+    }
+  }
+
+  Orthanc::ImageAccessor convertedAccessor(converted.GetAccessor());
+  Orthanc::ImageProcessing::Convert(convertedAccessor, decodedImage);
+
+  // Compress the converted image as a PNG file
+  OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor);
+
+  return 0;  // Success
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancContext::GetInstance().Initialize(context);
+    OrthancContext::GetInstance().LogWarning("Initializing GDCM decoding");
+    OrthancContext::GetInstance().Register("/instances/([^/]+)/(preview|image-uint8|image-uint16|image-int16)", DecodeImage);
+    return 0;
+  }
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancContext::GetInstance().LogWarning("Finalizing GDCM decoding");
+    OrthancContext::GetInstance().Finalize();
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "gdcm-decoding";
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "1.0";
+  }
+}
--- a/Resources/CMake/GoogleLogConfiguration.cmake	Thu Jul 10 10:43:47 2014 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Thu Jul 10 11:31:14 2014 +0200
@@ -65,22 +65,22 @@
   if (CMAKE_COMPILER_IS_GNUCXX)
     if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
       execute_process(
-        COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities-lsb.diff
+        COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff
         WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src
         )
     else()
       execute_process(
-        COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities.diff
+        COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff
         WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src
         )
     endif()
 
     execute_process(
-      COMMAND patch port.h ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-h.diff 
+      COMMAND patch port.h ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff 
       WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows
       )
     execute_process(
-      COMMAND patch port.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-cc.diff 
+      COMMAND patch port.cc ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff 
       WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows
       )
   endif()
@@ -91,18 +91,18 @@
     if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
       # Install the specific configuration for LSB SDK
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationLSB.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
       # Install the specific configuration for Mac OS
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationDarwin.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     else()
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     endif()
--- a/Resources/Configuration.json	Thu Jul 10 10:43:47 2014 +0200
+++ b/Resources/Configuration.json	Thu Jul 10 11:31:14 2014 +0200
@@ -28,11 +28,16 @@
   // of patients)
   "MaximumPatientCount" : 0,
   
-  // List of paths to the custom Lua scripts to load into this
-  // instance of Orthanc
+  // List of paths to the custom Lua scripts that are to be loaded
+  // into this instance of Orthanc
   "LuaScripts" : [
   ],
 
+  // List of paths to the plugins that are to be loaded into this
+  // instance of Orthanc (e.g. "/libPluginTest.so" for Linux, or
+  // "./PluginTest.dll" for Windows).
+  "Plugins" : [
+  ],
 
 
   /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/OrthancPlugin.doxygen	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,1792 @@
+# Doxyfile 1.8.1.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = "Orthanc Plugin SDK"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Documentation of the plugin interface of Orthanc"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = OrthancPluginDocumentation
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = NO
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = YES
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 0
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/OrthancCPlugin/OrthancCPlugin.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = _OrthancPlugin*
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = doc
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             = ORTHANC_PLUGIN_INLINE=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/PluginsTests.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * 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 "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <glog/logging.h>
+
+#include "../Plugins/Engine/PluginsManager.h"
+
+using namespace Orthanc;
+
+TEST(SharedLibrary, Basic)
+{
+#if defined(_WIN32)
+  SharedLibrary l("kernel32.dll");
+  ASSERT_THROW(l.GetFunction("world"), OrthancException);
+  ASSERT_TRUE(l.GetFunction("GetVersionExW") != NULL);
+  ASSERT_TRUE(l.HasFunction("GetVersionExW"));
+  ASSERT_FALSE(l.HasFunction("world"));
+
+#elif defined(__linux)
+  SharedLibrary l("libdl.so");
+  ASSERT_THROW(l.GetFunction("world"), OrthancException);
+  ASSERT_TRUE(l.GetFunction("dlopen") != NULL);
+  ASSERT_TRUE(l.HasFunction("dlclose"));
+  ASSERT_FALSE(l.HasFunction("world"));
+
+#else
+#error Support your platform here
+#endif
+}
--- a/UnitTestsSources/UnitTestsMain.cpp	Thu Jul 10 10:43:47 2014 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Thu Jul 10 11:31:14 2014 +0200
@@ -174,42 +174,69 @@
 }
 
 
-TEST(ParseGetQuery, Basic)
+TEST(ParseGetArguments, Basic)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c");
+  HttpHandler::ParseGetArguments(a, "aaa=baaa&bb=a&aa=c");
   ASSERT_EQ(3u, a.size());
   ASSERT_EQ(a["aaa"], "baaa");
   ASSERT_EQ(a["bb"], "a");
   ASSERT_EQ(a["aa"], "c");
 }
 
-TEST(ParseGetQuery, BasicEmpty)
+TEST(ParseGetArguments, BasicEmpty)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa");
+  HttpHandler::ParseGetArguments(a, "aaa&bb=aa&aa");
   ASSERT_EQ(3u, a.size());
   ASSERT_EQ(a["aaa"], "");
   ASSERT_EQ(a["bb"], "aa");
   ASSERT_EQ(a["aa"], "");
 }
 
-TEST(ParseGetQuery, Single)
+TEST(ParseGetArguments, Single)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa");
+  HttpHandler::ParseGetArguments(a, "aaa=baaa");
   ASSERT_EQ(1u, a.size());
   ASSERT_EQ(a["aaa"], "baaa");
 }
 
-TEST(ParseGetQuery, SingleEmpty)
+TEST(ParseGetArguments, SingleEmpty)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa");
+  HttpHandler::ParseGetArguments(a, "aaa");
   ASSERT_EQ(1u, a.size());
   ASSERT_EQ(a["aaa"], "");
 }
 
+TEST(ParseGetQuery, Test1)
+{
+  UriComponents uri;
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(uri, a, "/instances/test/world?aaa=baaa&bb=a&aa=c");
+  ASSERT_EQ(3u, uri.size());
+  ASSERT_EQ("instances", uri[0]);
+  ASSERT_EQ("test", uri[1]);
+  ASSERT_EQ("world", uri[2]);
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+  ASSERT_EQ(a["bb"], "a");
+  ASSERT_EQ(a["aa"], "c");
+}
+
+TEST(ParseGetQuery, Test2)
+{
+  UriComponents uri;
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(uri, a, "/instances/test/world");
+  ASSERT_EQ(3u, uri.size());
+  ASSERT_EQ("instances", uri[0]);
+  ASSERT_EQ("test", uri[1]);
+  ASSERT_EQ("world", uri[2]);
+  ASSERT_EQ(0u, a.size());
+}
+
 TEST(Uri, SplitUriComponents)
 {
   UriComponents c, d;