diff OrthancServer/Sources/main.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents OrthancServer/main.cpp@e3b3af80732d
children 05b8fd21089c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/main.cpp	Wed Jun 10 20:30:34 2020 +0200
@@ -0,0 +1,1718 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+#include "OrthancRestApi/OrthancRestApi.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "../Core/Compatibility.h"
+#include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/DicomNetworking/DicomAssociationParameters.h"
+#include "../Core/DicomNetworking/DicomServer.h"
+#include "../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../Core/HttpServer/FilesystemHttpHandler.h"
+#include "../Core/HttpServer/HttpServer.h"
+#include "../Core/Logging.h"
+#include "../Core/Lua/LuaFunctionCall.h"
+#include "../Plugins/Engine/OrthancPlugins.h"
+#include "EmbeddedResourceHttpHandler.h"
+#include "OrthancConfiguration.h"
+#include "OrthancFindRequestHandler.h"
+#include "OrthancGetRequestHandler.h"
+#include "OrthancInitialization.h"
+#include "OrthancMoveRequestHandler.h"
+#include "ServerContext.h"
+#include "ServerJobs/StorageCommitmentScpJob.h"
+#include "ServerToolbox.h"
+#include "StorageCommitmentReports.h"
+
+using namespace Orthanc;
+
+
+class OrthancStoreRequestHandler : public IStoreRequestHandler
+{
+private:
+  ServerContext& context_;
+
+public:
+  OrthancStoreRequestHandler(ServerContext& context) :
+    context_(context)
+  {
+  }
+
+
+  virtual void Handle(const std::string& dicomFile,
+                      const DicomMap& dicomSummary,
+                      const Json::Value& dicomJson,
+                      const std::string& remoteIp,
+                      const std::string& remoteAet,
+                      const std::string& calledAet) 
+  {
+    if (dicomFile.size() > 0)
+    {
+      DicomInstanceToStore toStore;
+      toStore.SetOrigin(DicomInstanceOrigin::FromDicomProtocol
+                        (remoteIp.c_str(), remoteAet.c_str(), calledAet.c_str()));
+      toStore.SetBuffer(dicomFile.c_str(), dicomFile.size());
+      toStore.SetSummary(dicomSummary);
+      toStore.SetJson(dicomJson);
+
+      std::string id;
+      context_.Store(id, toStore, StoreInstanceMode_Default);
+    }
+  }
+};
+
+
+
+class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler
+{
+private:
+  ServerContext& context_;
+  
+public:
+  OrthancStorageCommitmentRequestHandler(ServerContext& context) :
+    context_(context)
+  {
+  }
+
+  virtual void HandleRequest(const std::string& transactionUid,
+                             const std::vector<std::string>& referencedSopClassUids,
+                             const std::vector<std::string>& referencedSopInstanceUids,
+                             const std::string& remoteIp,
+                             const std::string& remoteAet,
+                             const std::string& calledAet)
+  {
+    if (referencedSopClassUids.size() != referencedSopInstanceUids.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    std::unique_ptr<StorageCommitmentScpJob> job(
+      new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet));
+
+    for (size_t i = 0; i < referencedSopClassUids.size(); i++)
+    {
+      job->AddInstance(referencedSopClassUids[i], referencedSopInstanceUids[i]);
+    }
+
+    job->MarkAsReady();
+
+    context_.GetJobsEngine().GetRegistry().Submit(job.release(), 0 /* default priority */);
+  }
+
+  virtual void HandleReport(const std::string& transactionUid,
+                            const std::vector<std::string>& successSopClassUids,
+                            const std::vector<std::string>& successSopInstanceUids,
+                            const std::vector<std::string>& failedSopClassUids,
+                            const std::vector<std::string>& failedSopInstanceUids,
+                            const std::vector<StorageCommitmentFailureReason>& failureReasons,
+                            const std::string& remoteIp,
+                            const std::string& remoteAet,
+                            const std::string& calledAet)
+  {
+    if (successSopClassUids.size() != successSopInstanceUids.size() ||
+        failedSopClassUids.size() != failedSopInstanceUids.size() ||
+        failedSopClassUids.size() != failureReasons.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    std::unique_ptr<StorageCommitmentReports::Report> report(
+      new StorageCommitmentReports::Report(remoteAet));
+
+    for (size_t i = 0; i < successSopClassUids.size(); i++)
+    {
+      report->AddSuccess(successSopClassUids[i], successSopInstanceUids[i]);
+    }
+
+    for (size_t i = 0; i < failedSopClassUids.size(); i++)
+    {
+      report->AddFailure(failedSopClassUids[i], failedSopInstanceUids[i], failureReasons[i]);
+    }
+
+    report->MarkAsComplete();
+
+    context_.GetStorageCommitmentReports().Store(transactionUid, report.release());
+  }
+};
+
+
+
+class ModalitiesFromConfiguration : public DicomServer::IRemoteModalities
+{
+public:
+  virtual bool IsSameAETitle(const std::string& aet1,
+                             const std::string& aet2) 
+  {
+    OrthancConfiguration::ReaderLock lock;
+    return lock.GetConfiguration().IsSameAETitle(aet1, aet2);
+  }
+
+  virtual bool LookupAETitle(RemoteModalityParameters& modality,
+                             const std::string& aet) 
+  {
+    OrthancConfiguration::ReaderLock lock;
+    return lock.GetConfiguration().LookupDicomModalityUsingAETitle(modality, aet);
+  }
+};
+
+
+class MyDicomServerFactory : 
+  public IStoreRequestHandlerFactory,
+  public IFindRequestHandlerFactory, 
+  public IMoveRequestHandlerFactory,
+  public IGetRequestHandlerFactory,
+  public IStorageCommitmentRequestHandlerFactory
+{
+private:
+  ServerContext& context_;
+
+public:
+  MyDicomServerFactory(ServerContext& context) : context_(context)
+  {
+  }
+
+  virtual IStoreRequestHandler* ConstructStoreRequestHandler()
+  {
+    return new OrthancStoreRequestHandler(context_);
+  }
+
+  virtual IFindRequestHandler* ConstructFindRequestHandler()
+  {
+    std::unique_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_));
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      result->SetMaxResults(lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0));
+      result->SetMaxInstances(lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0));
+    }
+
+    if (result->GetMaxResults() == 0)
+    {
+      LOG(INFO) << "No limit on the number of C-FIND results at the Patient, Study and Series levels";
+    }
+    else
+    {
+      LOG(INFO) << "Maximum " << result->GetMaxResults() 
+                << " results for C-FIND queries at the Patient, Study and Series levels";
+    }
+
+    if (result->GetMaxInstances() == 0)
+    {
+      LOG(INFO) << "No limit on the number of C-FIND results at the Instance level";
+    }
+    else
+    {
+      LOG(INFO) << "Maximum " << result->GetMaxInstances() 
+                << " instances will be returned for C-FIND queries at the Instance level";
+    }
+
+    return result.release();
+  }
+
+  virtual IMoveRequestHandler* ConstructMoveRequestHandler()
+  {
+    return new OrthancMoveRequestHandler(context_);
+  }
+  
+  virtual IGetRequestHandler* ConstructGetRequestHandler()
+  {
+    return new OrthancGetRequestHandler(context_);
+  }
+  
+  virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler()
+  {
+    return new OrthancStorageCommitmentRequestHandler(context_);
+  }
+  
+
+  void Done()
+  {
+  }
+};
+
+
+class OrthancApplicationEntityFilter : public IApplicationEntityFilter
+{
+private:
+  ServerContext&  context_;
+  bool            alwaysAllowEcho_;
+  bool            alwaysAllowStore_;
+
+public:
+  OrthancApplicationEntityFilter(ServerContext& context) :
+    context_(context)
+  {
+    OrthancConfiguration::ReaderLock lock;
+    alwaysAllowEcho_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowEcho", true);
+    alwaysAllowStore_ = lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowStore", true);
+  }
+
+  virtual bool IsAllowedConnection(const std::string& remoteIp,
+                                   const std::string& remoteAet,
+                                   const std::string& calledAet)
+  {
+    LOG(INFO) << "Incoming connection from AET " << remoteAet
+              << " on IP " << remoteIp << ", calling AET " << calledAet;
+
+    if (alwaysAllowEcho_ ||
+        alwaysAllowStore_)
+    {
+      return true;
+    }
+    else
+    {
+      OrthancConfiguration::ReaderLock lock;
+      return lock.GetConfiguration().IsKnownAETitle(remoteAet, remoteIp);
+    }
+  }
+
+  virtual bool IsAllowedRequest(const std::string& remoteIp,
+                                const std::string& remoteAet,
+                                const std::string& calledAet,
+                                DicomRequestType type)
+  {
+    LOG(INFO) << "Incoming " << EnumerationToString(type) << " request from AET "
+              << remoteAet << " on IP " << remoteIp << ", calling AET " << calledAet;
+    
+    if (type == DicomRequestType_Echo &&
+        alwaysAllowEcho_)
+    {
+      // Incoming C-Echo requests are always accepted, even from unknown AET
+      return true;
+    }
+    else if (type == DicomRequestType_Store &&
+             alwaysAllowStore_)
+    {
+      // Incoming C-Store requests are always accepted, even from unknown AET
+      return true;
+    }
+    else
+    {
+      OrthancConfiguration::ReaderLock lock;
+
+      RemoteModalityParameters modality;
+      if (lock.GetConfiguration().LookupDicomModalityUsingAETitle(modality, remoteAet))
+      {
+        return modality.IsRequestAllowed(type);
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+  virtual bool IsAllowedTransferSyntax(const std::string& remoteIp,
+                                       const std::string& remoteAet,
+                                       const std::string& calledAet,
+                                       TransferSyntax syntax)
+  {
+    std::string configuration;
+
+    switch (syntax)
+    {
+      case TransferSyntax_Deflated:
+        configuration = "DeflatedTransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Jpeg:
+        configuration = "JpegTransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Jpeg2000:
+        configuration = "Jpeg2000TransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_JpegLossless:
+        configuration = "JpegLosslessTransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Jpip:
+        configuration = "JpipTransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Mpeg2:
+        configuration = "Mpeg2TransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Mpeg4:
+        configuration = "Mpeg4TransferSyntaxAccepted";
+        break;
+
+      case TransferSyntax_Rle:
+        configuration = "RleTransferSyntaxAccepted";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    {
+      std::string name = "Is" + configuration;
+
+      LuaScripting::Lock lock(context_.GetLuaScripting());
+      
+      if (lock.GetLua().IsExistingFunction(name.c_str()))
+      {
+        LuaFunctionCall call(lock.GetLua(), name.c_str());
+        call.PushString(remoteAet);
+        call.PushString(remoteIp);
+        call.PushString(calledAet);
+        return call.ExecutePredicate();
+      }
+    }
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      return lock.GetConfiguration().GetBooleanParameter(configuration, true);
+    }
+  }
+
+
+  virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet)
+  {
+    static const char* configuration = "UnknownSopClassAccepted";
+
+    {
+      std::string lua = "Is" + std::string(configuration);
+
+      LuaScripting::Lock lock(context_.GetLuaScripting());
+      
+      if (lock.GetLua().IsExistingFunction(lua.c_str()))
+      {
+        LuaFunctionCall call(lock.GetLua(), lua.c_str());
+        call.PushString(remoteAet);
+        call.PushString(remoteIp);
+        call.PushString(calledAet);
+        return call.ExecutePredicate();
+      }
+    }
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      return lock.GetConfiguration().GetBooleanParameter(configuration, false);
+    }
+  }
+};
+
+
+class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter
+{
+private:
+  ServerContext&   context_;
+  OrthancPlugins*  plugins_;
+
+public:
+  MyIncomingHttpRequestFilter(ServerContext& context,
+                              OrthancPlugins* plugins) : 
+    context_(context),
+    plugins_(plugins)
+  {
+  }
+
+  virtual bool IsAllowed(HttpMethod method,
+                         const char* uri,
+                         const char* ip,
+                         const char* username,
+                         const IHttpHandler::Arguments& httpHeaders,
+                         const IHttpHandler::GetArguments& getArguments)
+  {
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (plugins_ != NULL &&
+        !plugins_->IsAllowed(method, uri, ip, username, httpHeaders, getArguments))
+    {
+      return false;
+    }
+#endif
+
+    static const char* HTTP_FILTER = "IncomingHttpRequestFilter";
+
+    LuaScripting::Lock lock(context_.GetLuaScripting());
+
+    // Test if the instance must be filtered out
+    if (lock.GetLua().IsExistingFunction(HTTP_FILTER))
+    {
+      LuaFunctionCall call(lock.GetLua(), HTTP_FILTER);
+
+      switch (method)
+      {
+        case HttpMethod_Get:
+          call.PushString("GET");
+          break;
+
+        case HttpMethod_Put:
+          call.PushString("PUT");
+          break;
+
+        case HttpMethod_Post:
+          call.PushString("POST");
+          break;
+
+        case HttpMethod_Delete:
+          call.PushString("DELETE");
+          break;
+
+        default:
+          return true;
+      }
+
+      call.PushString(uri);
+      call.PushString(ip);
+      call.PushString(username);
+      call.PushStringMap(httpHeaders);
+
+      if (!call.ExecutePredicate())
+      {
+        LOG(INFO) << "An incoming HTTP request has been discarded by the filter";
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+
+
+class MyHttpExceptionFormatter : public IHttpExceptionFormatter
+{
+private:
+  bool             describeErrors_;
+  OrthancPlugins*  plugins_;
+
+public:
+  MyHttpExceptionFormatter(bool describeErrors,
+                           OrthancPlugins* plugins) :
+    describeErrors_(describeErrors),
+    plugins_(plugins)
+  {
+  }
+
+  virtual void Format(HttpOutput& output,
+                      const OrthancException& exception,
+                      HttpMethod method,
+                      const char* uri)
+  {
+    {
+      bool isPlugin = false;
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (plugins_ != NULL)
+      {
+        plugins_->GetErrorDictionary().LogError(exception.GetErrorCode(), true);
+        isPlugin = true;
+      }
+#endif
+
+      if (!isPlugin)
+      {
+        LOG(ERROR) << "Exception in the HTTP handler: " << exception.What();
+      }
+    }      
+
+    Json::Value message = Json::objectValue;
+    ErrorCode errorCode = exception.GetErrorCode();
+    HttpStatus httpStatus = exception.GetHttpStatus();
+
+    {
+      bool isPlugin = false;
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (plugins_ != NULL &&
+          plugins_->GetErrorDictionary().Format(message, httpStatus, exception))
+      {
+        errorCode = ErrorCode_Plugin;
+        isPlugin = true;
+      }
+#endif
+
+      if (!isPlugin)
+      {
+        message["Message"] = exception.What();
+      }
+    }
+
+    if (!describeErrors_)
+    {
+      output.SendStatus(httpStatus);
+    }
+    else
+    {
+      message["Method"] = EnumerationToString(method);
+      message["Uri"] = uri;
+      message["HttpError"] = EnumerationToString(httpStatus);
+      message["HttpStatus"] = httpStatus;
+      message["OrthancError"] = EnumerationToString(errorCode);
+      message["OrthancStatus"] = errorCode;
+
+      if (exception.HasDetails())
+      {
+        message["Details"] = exception.GetDetails();
+      }
+
+      std::string info = message.toStyledString();
+      output.SendStatus(httpStatus, info);
+    }
+  }
+};
+
+
+
+static void PrintHelp(const char* path)
+{
+  std::cout 
+    << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
+    << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." << std::endl
+    << std::endl
+    << "The \"CONFIGURATION\" argument can be a single file or a directory. In the " << std::endl
+    << "case of a directory, all the JSON files it contains will be merged. " << std::endl
+    << "If no configuration path is given on the command line, a set of default " << std::endl
+    << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl
+    << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl
+    << std::endl
+    << "Command-line options:" << std::endl
+    << "  --help\t\tdisplay this help and exit" << std::endl
+    << "  --logdir=[dir]\tdirectory where to store the log files" << std::endl
+    << "\t\t\t(by default, the log is dumped to stderr)" << std::endl
+    << "  --logfile=[file]\tfile where to store the log of Orthanc" << std::endl
+    << "\t\t\t(by default, the log is dumped to stderr)" << std::endl
+    << "  --config=[file]\tcreate a sample configuration file and exit" << std::endl
+    << "\t\t\t(if file is \"-\", dumps to stdout)" << std::endl
+    << "  --errors\t\tprint the supported error codes and exit" << std::endl
+    << "  --verbose\t\tbe verbose in logs" << std::endl
+    << "  --trace\t\thighest verbosity in logs (for debug)" << std::endl
+    << "  --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl
+    << "\t\t\tdatabase (beware that the database will become" << std::endl
+    << "\t\t\tincompatible with former versions of Orthanc)" << std::endl
+    << "  --no-jobs\t\tDon't restart the jobs that were stored during" << std::endl
+    << "\t\t\tthe last execution of Orthanc" << std::endl
+    << "  --version\t\toutput version information and exit" << std::endl
+    << std::endl
+    << "Exit status:" << std::endl
+    << "   0 if success," << std::endl
+#if defined(_WIN32)
+    << "!= 0 if error (use the --errors option to get the list of possible errors)." << std::endl
+#else
+    << "  -1 if error (have a look at the logs)." << std::endl
+#endif
+    << std::endl;
+}
+
+
+static void PrintVersion(const char* path)
+{
+  std::cout
+    << path << " " << ORTHANC_VERSION << std::endl
+    << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
+    << "Copyright (C) 2017-2020 Osimis S.A. (Belgium)" << std::endl
+    << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl
+    << "This is free software: you are free to change and redistribute it." << std::endl
+    << "There is NO WARRANTY, to the extent permitted by law." << std::endl
+    << std::endl
+    << "Written by Sebastien Jodogne <s.jodogne@orthanc-labs.com>" << std::endl;
+}
+
+
+static void PrintErrorCode(ErrorCode code, const char* description)
+{
+  std::cout 
+    << std::right << std::setw(16) 
+    << static_cast<int>(code)
+    << "   " << description << std::endl;
+}
+
+
+static void PrintErrors(const char* path)
+{
+  std::cout
+    << path << " " << ORTHANC_VERSION << std::endl
+    << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." 
+    << std::endl << std::endl
+    << "List of error codes that could be returned by Orthanc:" 
+    << std::endl << std::endl;
+
+  // The content of the following brackets is automatically generated
+  // by the "GenerateErrorCodes.py" script
+  {
+    PrintErrorCode(ErrorCode_InternalError, "Internal error");
+    PrintErrorCode(ErrorCode_Success, "Success");
+    PrintErrorCode(ErrorCode_Plugin, "Error encountered within the plugin engine");
+    PrintErrorCode(ErrorCode_NotImplemented, "Not implemented yet");
+    PrintErrorCode(ErrorCode_ParameterOutOfRange, "Parameter out of range");
+    PrintErrorCode(ErrorCode_NotEnoughMemory, "The server hosting Orthanc is running out of memory");
+    PrintErrorCode(ErrorCode_BadParameterType, "Bad type for a parameter");
+    PrintErrorCode(ErrorCode_BadSequenceOfCalls, "Bad sequence of calls");
+    PrintErrorCode(ErrorCode_InexistentItem, "Accessing an inexistent item");
+    PrintErrorCode(ErrorCode_BadRequest, "Bad request");
+    PrintErrorCode(ErrorCode_NetworkProtocol, "Error in the network protocol");
+    PrintErrorCode(ErrorCode_SystemCommand, "Error while calling a system command");
+    PrintErrorCode(ErrorCode_Database, "Error with the database engine");
+    PrintErrorCode(ErrorCode_UriSyntax, "Badly formatted URI");
+    PrintErrorCode(ErrorCode_InexistentFile, "Inexistent file");
+    PrintErrorCode(ErrorCode_CannotWriteFile, "Cannot write to file");
+    PrintErrorCode(ErrorCode_BadFileFormat, "Bad file format");
+    PrintErrorCode(ErrorCode_Timeout, "Timeout");
+    PrintErrorCode(ErrorCode_UnknownResource, "Unknown resource");
+    PrintErrorCode(ErrorCode_IncompatibleDatabaseVersion, "Incompatible version of the database");
+    PrintErrorCode(ErrorCode_FullStorage, "The file storage is full");
+    PrintErrorCode(ErrorCode_CorruptedFile, "Corrupted file (e.g. inconsistent MD5 hash)");
+    PrintErrorCode(ErrorCode_InexistentTag, "Inexistent tag");
+    PrintErrorCode(ErrorCode_ReadOnly, "Cannot modify a read-only data structure");
+    PrintErrorCode(ErrorCode_IncompatibleImageFormat, "Incompatible format of the images");
+    PrintErrorCode(ErrorCode_IncompatibleImageSize, "Incompatible size of the images");
+    PrintErrorCode(ErrorCode_SharedLibrary, "Error while using a shared library (plugin)");
+    PrintErrorCode(ErrorCode_UnknownPluginService, "Plugin invoking an unknown service");
+    PrintErrorCode(ErrorCode_UnknownDicomTag, "Unknown DICOM tag");
+    PrintErrorCode(ErrorCode_BadJson, "Cannot parse a JSON document");
+    PrintErrorCode(ErrorCode_Unauthorized, "Bad credentials were provided to an HTTP request");
+    PrintErrorCode(ErrorCode_BadFont, "Badly formatted font file");
+    PrintErrorCode(ErrorCode_DatabasePlugin, "The plugin implementing a custom database back-end does not fulfill the proper interface");
+    PrintErrorCode(ErrorCode_StorageAreaPlugin, "Error in the plugin implementing a custom storage area");
+    PrintErrorCode(ErrorCode_EmptyRequest, "The request is empty");
+    PrintErrorCode(ErrorCode_NotAcceptable, "Cannot send a response which is acceptable according to the Accept HTTP header");
+    PrintErrorCode(ErrorCode_NullPointer, "Cannot handle a NULL pointer");
+    PrintErrorCode(ErrorCode_DatabaseUnavailable, "The database is currently not available (probably a transient situation)");
+    PrintErrorCode(ErrorCode_CanceledJob, "This job was canceled");
+    PrintErrorCode(ErrorCode_BadGeometry, "Geometry error encountered in Stone");
+    PrintErrorCode(ErrorCode_SslInitialization, "Cannot initialize SSL encryption, check out your certificates");
+    PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened");
+    PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open");
+    PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database");
+    PrintErrorCode(ErrorCode_SQLiteStatementAlreadyUsed, "SQLite: This cached statement is already being referred to");
+    PrintErrorCode(ErrorCode_SQLiteExecute, "SQLite: Cannot execute a command");
+    PrintErrorCode(ErrorCode_SQLiteRollbackWithoutTransaction, "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)");
+    PrintErrorCode(ErrorCode_SQLiteCommitWithoutTransaction, "SQLite: Committing a nonexistent transaction");
+    PrintErrorCode(ErrorCode_SQLiteRegisterFunction, "SQLite: Unable to register a function");
+    PrintErrorCode(ErrorCode_SQLiteFlush, "SQLite: Unable to flush the database");
+    PrintErrorCode(ErrorCode_SQLiteCannotRun, "SQLite: Cannot run a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteCannotStep, "SQLite: Cannot step over a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bing a value while out of range (serious error)");
+    PrintErrorCode(ErrorCode_SQLitePrepareStatement, "SQLite: Cannot prepare a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteTransactionAlreadyStarted, "SQLite: Beginning the same transaction twice");
+    PrintErrorCode(ErrorCode_SQLiteTransactionCommit, "SQLite: Failure when committing the transaction");
+    PrintErrorCode(ErrorCode_SQLiteTransactionBegin, "SQLite: Cannot start a transaction");
+    PrintErrorCode(ErrorCode_DirectoryOverFile, "The directory to be created is already occupied by a regular file");
+    PrintErrorCode(ErrorCode_FileStorageCannotWrite, "Unable to create a subdirectory or a file in the file storage");
+    PrintErrorCode(ErrorCode_DirectoryExpected, "The specified path does not point to a directory");
+    PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is privileged or already in use");
+    PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is privileged or already in use");
+    PrintErrorCode(ErrorCode_BadHttpStatusInRest, "This HTTP status is not allowed in a REST API");
+    PrintErrorCode(ErrorCode_RegularFileExpected, "The specified path does not point to a regular file");
+    PrintErrorCode(ErrorCode_PathToExecutable, "Unable to get the path to the executable");
+    PrintErrorCode(ErrorCode_MakeDirectory, "Cannot create a directory");
+    PrintErrorCode(ErrorCode_BadApplicationEntityTitle, "An application entity title (AET) cannot be empty or be longer than 16 characters");
+    PrintErrorCode(ErrorCode_NoCFindHandler, "No request handler factory for DICOM C-FIND SCP");
+    PrintErrorCode(ErrorCode_NoCMoveHandler, "No request handler factory for DICOM C-MOVE SCP");
+    PrintErrorCode(ErrorCode_NoCStoreHandler, "No request handler factory for DICOM C-STORE SCP");
+    PrintErrorCode(ErrorCode_NoApplicationEntityFilter, "No application entity filter");
+    PrintErrorCode(ErrorCode_NoSopClassOrInstance, "DicomUserConnection: Unable to find the SOP class and instance");
+    PrintErrorCode(ErrorCode_NoPresentationContext, "DicomUserConnection: No acceptable presentation context for modality");
+    PrintErrorCode(ErrorCode_DicomFindUnavailable, "DicomUserConnection: The C-FIND command is not supported by the remote SCP");
+    PrintErrorCode(ErrorCode_DicomMoveUnavailable, "DicomUserConnection: The C-MOVE command is not supported by the remote SCP");
+    PrintErrorCode(ErrorCode_CannotStoreInstance, "Cannot store an instance");
+    PrintErrorCode(ErrorCode_CreateDicomNotString, "Only string values are supported when creating DICOM instances");
+    PrintErrorCode(ErrorCode_CreateDicomOverrideTag, "Trying to override a value inherited from a parent module");
+    PrintErrorCode(ErrorCode_CreateDicomUseContent, "Use \"Content\" to inject an image into a new DICOM instance");
+    PrintErrorCode(ErrorCode_CreateDicomNoPayload, "No payload is present for one instance in the series");
+    PrintErrorCode(ErrorCode_CreateDicomUseDataUriScheme, "The payload of the DICOM instance must be specified according to Data URI scheme");
+    PrintErrorCode(ErrorCode_CreateDicomBadParent, "Trying to attach a new DICOM instance to an inexistent resource");
+    PrintErrorCode(ErrorCode_CreateDicomParentIsInstance, "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)");
+    PrintErrorCode(ErrorCode_CreateDicomParentEncoding, "Unable to get the encoding of the parent resource");
+    PrintErrorCode(ErrorCode_UnknownModality, "Unknown modality");
+    PrintErrorCode(ErrorCode_BadJobOrdering, "Bad ordering of filters in a job");
+    PrintErrorCode(ErrorCode_JsonToLuaTable, "Cannot convert the given JSON object to a Lua table");
+    PrintErrorCode(ErrorCode_CannotCreateLua, "Cannot create the Lua context");
+    PrintErrorCode(ErrorCode_CannotExecuteLua, "Cannot execute a Lua command");
+    PrintErrorCode(ErrorCode_LuaAlreadyExecuted, "Arguments cannot be pushed after the Lua function is executed");
+    PrintErrorCode(ErrorCode_LuaBadOutput, "The Lua function does not give the expected number of outputs");
+    PrintErrorCode(ErrorCode_NotLuaPredicate, "The Lua function is not a predicate (only true/false outputs allowed)");
+    PrintErrorCode(ErrorCode_LuaReturnsNoString, "The Lua function does not return a string");
+    PrintErrorCode(ErrorCode_StorageAreaAlreadyRegistered, "Another plugin has already registered a custom storage area");
+    PrintErrorCode(ErrorCode_DatabaseBackendAlreadyRegistered, "Another plugin has already registered a custom database back-end");
+    PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization");
+    PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support");
+    PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series");
+    PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP");
+    PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists");
+    PrintErrorCode(ErrorCode_NoCGetHandler, "No request handler factory for DICOM C-GET SCP");
+    PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)");
+    PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type");
+  }
+
+  std::cout << std::endl;
+}
+
+
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+static void LoadPlugins(OrthancPlugins& plugins)
+{
+  std::list<std::string> path;
+
+  {
+    OrthancConfiguration::ReaderLock lock;
+    lock.GetConfiguration().GetListOfStringsParameter(path, "Plugins");
+  }
+
+  for (std::list<std::string>::const_iterator
+         it = path.begin(); it != path.end(); ++it)
+  {
+    std::string path;
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      path = lock.GetConfiguration().InterpretStringParameterAsPath(*it);
+    }
+
+    LOG(WARNING) << "Loading plugin(s) from: " << path;
+    plugins.GetManager().RegisterPlugin(path);
+  }  
+}
+#endif
+
+
+
+// Returns "true" if restart is required
+static bool WaitForExit(ServerContext& context,
+                        OrthancRestApi& restApi)
+{
+  LOG(WARNING) << "Orthanc has started";
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+  if (context.HasPlugins())
+  {
+    context.GetPlugins().SignalOrthancStarted();
+  }
+#endif
+
+  context.GetLuaScripting().Start();
+  context.GetLuaScripting().Execute("Initialize");
+
+  bool restart;
+
+  for (;;)
+  {
+    ServerBarrierEvent event = SystemToolbox::ServerBarrier(restApi.LeaveBarrierFlag());
+    restart = restApi.IsResetRequestReceived();
+
+    if (!restart && 
+        event == ServerBarrierEvent_Reload)
+    {
+      // Handling of SIGHUP
+
+      OrthancConfiguration::ReaderLock lock;
+      if (lock.GetConfiguration().HasConfigurationChanged())
+      {
+        LOG(WARNING) << "A SIGHUP signal has been received, resetting Orthanc";
+        Logging::Flush();
+        restart = true;
+        break;
+      }
+      else
+      {
+        LOG(WARNING) << "A SIGHUP signal has been received, but is ignored "
+                     << "as the configuration has not changed on the disk";
+        Logging::Flush();
+        continue;
+      }
+    }
+    else
+    {
+      break;
+    }
+  }
+
+  context.GetLuaScripting().Execute("Finalize");
+  context.GetLuaScripting().Stop();
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+  if (context.HasPlugins())
+  {
+    context.GetPlugins().SignalOrthancStopped();
+  }
+#endif
+
+  if (restart)
+  {
+    LOG(WARNING) << "Reset request received, restarting Orthanc";
+  }
+
+  // We're done
+  LOG(WARNING) << "Orthanc is stopping";
+
+  return restart;
+}
+
+
+
+static bool StartHttpServer(ServerContext& context,
+                            OrthancRestApi& restApi,
+                            OrthancPlugins* plugins)
+{
+  bool httpServerEnabled;
+
+  {
+    OrthancConfiguration::ReaderLock lock;
+    httpServerEnabled = lock.GetConfiguration().GetBooleanParameter("HttpServerEnabled", true);
+  }
+
+  if (!httpServerEnabled)
+  {
+    LOG(WARNING) << "The HTTP server is disabled";
+    return WaitForExit(context, restApi);
+  }
+  else
+  {
+    MyIncomingHttpRequestFilter httpFilter(context, plugins);
+    HttpServer httpServer;
+    bool httpDescribeErrors;
+
+#if ORTHANC_ENABLE_MONGOOSE == 1
+    const bool defaultKeepAlive = false;
+#elif ORTHANC_ENABLE_CIVETWEB == 1
+    const bool defaultKeepAlive = true;
+#else
+#  error "Either Mongoose or Civetweb must be enabled to compile this file"
+#endif
+  
+    {
+      OrthancConfiguration::ReaderLock lock;
+      
+      httpDescribeErrors = lock.GetConfiguration().GetBooleanParameter("HttpDescribeErrors", true);
+  
+      // HTTP server
+      httpServer.SetThreadsCount(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpThreadsCount", 50));
+      httpServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042));
+      httpServer.SetRemoteAccessAllowed(lock.GetConfiguration().GetBooleanParameter("RemoteAccessAllowed", false));
+      httpServer.SetKeepAliveEnabled(lock.GetConfiguration().GetBooleanParameter("KeepAlive", defaultKeepAlive));
+      httpServer.SetHttpCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("HttpCompressionEnabled", true));
+      httpServer.SetTcpNoDelay(lock.GetConfiguration().GetBooleanParameter("TcpNoDelay", true));
+      httpServer.SetRequestTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpRequestTimeout", 30));
+
+      // Let's assume that the HTTP server is secure
+      context.SetHttpServerSecure(true);
+
+      bool authenticationEnabled;
+      if (lock.GetConfiguration().LookupBooleanParameter(authenticationEnabled, "AuthenticationEnabled"))
+      {
+        httpServer.SetAuthenticationEnabled(authenticationEnabled);
+
+        if (httpServer.IsRemoteAccessAllowed() &&
+            !authenticationEnabled)
+        {
+          LOG(WARNING) << "====> Remote access is enabled while user authentication is explicitly disabled, "
+                       << "your setup is POSSIBLY INSECURE <====";
+          context.SetHttpServerSecure(false);
+        }
+      }
+      else if (httpServer.IsRemoteAccessAllowed())
+      {
+        // Starting with Orthanc 1.5.8, it is impossible to enable
+        // remote access without having explicitly disabled user
+        // authentication.
+        LOG(WARNING) << "Remote access is allowed but \"AuthenticationEnabled\" is not in the configuration, "
+                     << "automatically enabling HTTP authentication for security";          
+        httpServer.SetAuthenticationEnabled(true);
+      }
+      else
+      {
+        // If Orthanc only listens on the localhost, it is OK to have
+        // "AuthenticationEnabled" disabled
+        httpServer.SetAuthenticationEnabled(false);
+      }
+
+      bool hasUsers = lock.GetConfiguration().SetupRegisteredUsers(httpServer);
+
+      if (httpServer.IsAuthenticationEnabled() &&
+          !hasUsers)
+      {
+        if (httpServer.IsRemoteAccessAllowed())
+        {
+          /**
+           * Starting with Orthanc 1.5.8, if no user is explicitly
+           * defined while remote access is allowed, we create a
+           * default user, and Orthanc Explorer shows a warning
+           * message about an "Insecure setup". This convention is
+           * used in Docker images "jodogne/orthanc",
+           * "jodogne/orthanc-plugins" and "osimis/orthanc".
+           **/
+          LOG(WARNING) << "====> HTTP authentication is enabled, but no user is declared. "
+                       << "Creating a default user: Review your configuration option \"RegisteredUsers\". "
+                       << "Your setup is INSECURE <====";
+
+          context.SetHttpServerSecure(false);
+
+          // This is the username/password of the default user in Orthanc.
+          httpServer.RegisterUser("orthanc", "orthanc");
+        }
+        else
+        {
+          LOG(WARNING) << "HTTP authentication is enabled, but no user is declared, "
+                       << "check the value of configuration option \"RegisteredUsers\"";
+        }
+      }
+      
+      if (lock.GetConfiguration().GetBooleanParameter("SslEnabled", false))
+      {
+        std::string certificate = lock.GetConfiguration().InterpretStringParameterAsPath(
+          lock.GetConfiguration().GetStringParameter("SslCertificate", "certificate.pem"));
+        httpServer.SetSslEnabled(true);
+        httpServer.SetSslCertificate(certificate.c_str());
+      }
+      else
+      {
+        httpServer.SetSslEnabled(false);
+      }
+
+      if (lock.GetConfiguration().GetBooleanParameter("ExecuteLuaEnabled", false))
+      {
+        context.SetExecuteLuaEnabled(true);
+        LOG(WARNING) << "====> Remote LUA script execution is enabled.  Review your configuration option \"ExecuteLuaEnabled\". "
+                     << "Your setup is POSSIBLY INSECURE <====";
+      }
+      else
+      {
+        context.SetExecuteLuaEnabled(false);
+        LOG(WARNING) << "Remote LUA script execution is disabled";
+      }
+    }
+
+    MyHttpExceptionFormatter exceptionFormatter(httpDescribeErrors, plugins);
+        
+    httpServer.SetIncomingHttpRequestFilter(httpFilter);
+    httpServer.SetHttpExceptionFormatter(exceptionFormatter);
+    httpServer.Register(context.GetHttpHandler());
+
+    if (httpServer.GetPortNumber() < 1024)
+    {
+      LOG(WARNING) << "The HTTP port is privileged (" 
+                   << httpServer.GetPortNumber() << " is below 1024), "
+                   << "make sure you run Orthanc as root/administrator";
+    }
+
+    httpServer.Start();
+  
+    bool restart = WaitForExit(context, restApi);
+
+    httpServer.Stop();
+    LOG(WARNING) << "    HTTP server has stopped";
+
+    return restart;
+  }
+}
+
+
+static bool StartDicomServer(ServerContext& context,
+                             OrthancRestApi& restApi,
+                             OrthancPlugins* plugins)
+{
+  bool dicomServerEnabled;
+
+  {
+    OrthancConfiguration::ReaderLock lock;
+    dicomServerEnabled = lock.GetConfiguration().GetBooleanParameter("DicomServerEnabled", true);
+  }
+
+  if (!dicomServerEnabled)
+  {
+    LOG(WARNING) << "The DICOM server is disabled";
+    return StartHttpServer(context, restApi, plugins);
+  }
+  else
+  {
+    MyDicomServerFactory serverFactory(context);
+    OrthancApplicationEntityFilter dicomFilter(context);
+    ModalitiesFromConfiguration modalities;
+  
+    // Setup the DICOM server  
+    DicomServer dicomServer;
+    dicomServer.SetRemoteModalities(modalities);
+    dicomServer.SetStoreRequestHandlerFactory(serverFactory);
+    dicomServer.SetMoveRequestHandlerFactory(serverFactory);
+    dicomServer.SetGetRequestHandlerFactory(serverFactory);
+    dicomServer.SetFindRequestHandlerFactory(serverFactory);
+    dicomServer.SetStorageCommitmentRequestHandlerFactory(serverFactory);
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      dicomServer.SetCalledApplicationEntityTitleCheck(lock.GetConfiguration().GetBooleanParameter("DicomCheckCalledAet", false));
+      dicomServer.SetAssociationTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScpTimeout", 30));
+      dicomServer.SetPortNumber(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242));
+      dicomServer.SetApplicationEntityTitle(lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC"));
+    }
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (plugins != NULL)
+    {
+      if (plugins->HasWorklistHandler())
+      {
+        dicomServer.SetWorklistRequestHandlerFactory(*plugins);
+      }
+
+      if (plugins->HasFindHandler())
+      {
+        dicomServer.SetFindRequestHandlerFactory(*plugins);
+      }
+
+      if (plugins->HasMoveHandler())
+      {
+        dicomServer.SetMoveRequestHandlerFactory(*plugins);
+      }
+    }
+#endif
+
+    dicomServer.SetApplicationEntityFilter(dicomFilter);
+
+    if (dicomServer.GetPortNumber() < 1024)
+    {
+      LOG(WARNING) << "The DICOM port is privileged (" 
+                   << dicomServer.GetPortNumber() << " is below 1024), "
+                   << "make sure you run Orthanc as root/administrator";
+    }
+
+    dicomServer.Start();
+    LOG(WARNING) << "DICOM server listening with AET " << dicomServer.GetApplicationEntityTitle() 
+                 << " on port: " << dicomServer.GetPortNumber();
+
+    bool restart = false;
+    ErrorCode error = ErrorCode_Success;
+
+    try
+    {
+      restart = StartHttpServer(context, restApi, plugins);
+    }
+    catch (OrthancException& e)
+    {
+      error = e.GetErrorCode();
+    }
+
+    dicomServer.Stop();
+    LOG(WARNING) << "    DICOM server has stopped";
+
+    serverFactory.Done();
+
+    if (error != ErrorCode_Success)
+    {
+      throw OrthancException(error);
+    }
+
+    return restart;
+  }
+}
+
+
+static bool ConfigureHttpHandler(ServerContext& context,
+                                 OrthancPlugins *plugins,
+                                 bool loadJobsFromDatabase)
+{
+#if ORTHANC_ENABLE_PLUGINS == 1
+  // By order of priority, first apply the "plugins" layer, so that
+  // plugins can overwrite the built-in REST API of Orthanc
+  if (plugins)
+  {
+    assert(context.HasPlugins());
+    context.GetHttpHandler().Register(*plugins, false);
+  }
+#endif
+  
+  // Secondly, apply the "static resources" layer
+#if ORTHANC_STANDALONE == 1
+  EmbeddedResourceHttpHandler staticResources("/app", ServerResources::ORTHANC_EXPLORER);
+#else
+  FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer");
+#endif
+
+  context.GetHttpHandler().Register(staticResources, false);
+
+  // Thirdly, consider the built-in REST API of Orthanc
+  OrthancRestApi restApi(context);
+  context.GetHttpHandler().Register(restApi, true);
+
+  context.SetupJobsEngine(false /* not running unit tests */, loadJobsFromDatabase);
+
+  bool restart = StartDicomServer(context, restApi, plugins);
+
+  context.Stop();
+
+  return restart;
+}
+
+
+static void UpgradeDatabase(IDatabaseWrapper& database,
+                            IStorageArea& storageArea)
+{
+  // Upgrade the schema of the database, if needed
+  unsigned int currentVersion = database.GetDatabaseVersion();
+
+  LOG(WARNING) << "Starting the upgrade of the database schema";
+  LOG(WARNING) << "Current database version: " << currentVersion;
+  LOG(WARNING) << "Database version expected by Orthanc: " << ORTHANC_DATABASE_VERSION;
+  
+  if (currentVersion == ORTHANC_DATABASE_VERSION)
+  {
+    LOG(WARNING) << "No upgrade is needed, start Orthanc without the \"--upgrade\" argument";
+    return;
+  }
+
+  if (currentVersion > ORTHANC_DATABASE_VERSION)
+  {
+    throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
+                           "The version of the database schema (" +
+                           boost::lexical_cast<std::string>(currentVersion) +
+                           ") is too recent for this version of Orthanc. Please upgrade Orthanc.");
+  }
+
+  LOG(WARNING) << "Upgrading the database from schema version "
+               << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
+
+  try
+  {
+    database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
+  }
+  catch (OrthancException&)
+  {
+    LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: "
+               << "http://book.orthanc-server.com/users/replication.html";
+    throw;
+  }
+    
+  // Sanity check
+  currentVersion = database.GetDatabaseVersion();
+  if (ORTHANC_DATABASE_VERSION != currentVersion)
+  {
+    throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
+                           "The database schema was not properly upgraded, it is still at version " +
+                           boost::lexical_cast<std::string>(currentVersion));
+  }
+  else
+  {
+    LOG(WARNING) << "The database schema was successfully upgraded, "
+                 << "you can now start Orthanc without the \"--upgrade\" argument";
+  }
+}
+
+
+
+namespace
+{
+  class ServerContextConfigurator : public boost::noncopyable
+  {
+  private:
+    ServerContext&   context_;
+    OrthancPlugins*  plugins_;
+
+  public:
+    ServerContextConfigurator(ServerContext& context,
+                              OrthancPlugins* plugins) :
+      context_(context),
+      plugins_(plugins)
+    {
+      {
+        OrthancConfiguration::WriterLock lock;
+        lock.GetConfiguration().SetServerIndex(context.GetIndex());
+      }
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (plugins_ != NULL)
+      {
+        plugins_->SetServerContext(context_);
+        context_.SetPlugins(*plugins_);
+      }
+#endif
+    }
+
+    ~ServerContextConfigurator()
+    {
+      {
+        OrthancConfiguration::WriterLock lock;
+        lock.GetConfiguration().ResetServerIndex();
+      }
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+      if (plugins_ != NULL)
+      {
+        plugins_->ResetServerContext();
+        context_.ResetPlugins();
+      }
+#endif
+    }
+  };
+}
+
+
+static bool ConfigureServerContext(IDatabaseWrapper& database,
+                                   IStorageArea& storageArea,
+                                   OrthancPlugins *plugins,
+                                   bool loadJobsFromDatabase)
+{
+  size_t maxCompletedJobs;
+  
+  {
+    OrthancConfiguration::ReaderLock lock;
+
+    // These configuration options must be set before creating the
+    // ServerContext, otherwise the possible Lua scripts will not be
+    // able to properly issue HTTP/HTTPS queries
+    HttpClient::ConfigureSsl(lock.GetConfiguration().GetBooleanParameter("HttpsVerifyPeers", true),
+                             lock.GetConfiguration().InterpretStringParameterAsPath
+                             (lock.GetConfiguration().GetStringParameter("HttpsCACertificates", "")));
+    HttpClient::SetDefaultVerbose(lock.GetConfiguration().GetBooleanParameter("HttpVerbose", false));
+
+    // The value "0" below makes the class HttpClient use its default
+    // value (DEFAULT_HTTP_TIMEOUT = 60 seconds in Orthanc 1.5.7)
+    HttpClient::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("HttpTimeout", 0));
+    
+    HttpClient::SetDefaultProxy(lock.GetConfiguration().GetStringParameter("HttpProxy", ""));
+    
+    DicomAssociationParameters::SetDefaultTimeout(lock.GetConfiguration().GetUnsignedIntegerParameter("DicomScuTimeout", 10));
+
+    maxCompletedJobs = lock.GetConfiguration().GetUnsignedIntegerParameter("JobsHistorySize", 10);
+
+    if (maxCompletedJobs == 0)
+    {
+      LOG(WARNING) << "Setting option \"JobsHistorySize\" to zero is not recommended";
+    }
+  }
+  
+  ServerContext context(database, storageArea, false /* not running unit tests */, maxCompletedJobs);
+
+  {
+    OrthancConfiguration::ReaderLock lock;
+
+    context.SetCompressionEnabled(lock.GetConfiguration().GetBooleanParameter("StorageCompression", false));
+    context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true));
+
+    // New option in Orthanc 1.4.2
+    context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false));
+
+    try
+    {
+      context.GetIndex().SetMaximumPatientCount(lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumPatientCount", 0));
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumPatientCount(0);
+    }
+
+    try
+    {
+      uint64_t size = lock.GetConfiguration().GetUnsignedIntegerParameter("MaximumStorageSize", 0);
+      context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
+    }
+    catch (...)
+    {
+      context.GetIndex().SetMaximumStorageSize(0);
+    }
+  }
+
+  {
+    ServerContextConfigurator configurator(context, plugins);
+
+    {
+      OrthancConfiguration::WriterLock lock;
+      lock.GetConfiguration().LoadModalitiesAndPeers();
+    }
+
+    return ConfigureHttpHandler(context, plugins, loadJobsFromDatabase);
+  }
+}
+
+
+static bool ConfigureDatabase(IDatabaseWrapper& database,
+                              IStorageArea& storageArea,
+                              OrthancPlugins *plugins,
+                              bool upgradeDatabase,
+                              bool loadJobsFromDatabase)
+{
+  database.Open();
+
+  unsigned int currentVersion = database.GetDatabaseVersion();
+
+  if (upgradeDatabase)
+  {
+    UpgradeDatabase(database, storageArea);
+    return false;  // Stop and don't restart Orthanc (cf. issue 29)
+  }
+  else if (currentVersion != ORTHANC_DATABASE_VERSION)
+  {
+    throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
+                           "The database schema must be upgraded from version " +
+                           boost::lexical_cast<std::string>(currentVersion) + " to " +
+                           boost::lexical_cast<std::string>(ORTHANC_DATABASE_VERSION) +
+                           ": Please run Orthanc with the \"--upgrade\" argument");
+  }
+
+  bool success = ConfigureServerContext
+    (database, storageArea, plugins, loadJobsFromDatabase);
+
+  database.Close();
+
+  return success;
+}
+
+
+static bool ConfigurePlugins(int argc, 
+                             char* argv[],
+                             bool upgradeDatabase,
+                             bool loadJobsFromDatabase)
+{
+  std::unique_ptr<IDatabaseWrapper>  databasePtr;
+  std::unique_ptr<IStorageArea>  storage;
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+  OrthancPlugins plugins;
+  plugins.SetCommandLineArguments(argc, argv);
+  LoadPlugins(plugins);
+
+  IDatabaseWrapper* database = NULL;
+  if (plugins.HasDatabaseBackend())
+  {
+    LOG(WARNING) << "Using a custom database from plugins";
+    database = &plugins.GetDatabaseBackend();
+  }
+  else
+  {
+    databasePtr.reset(CreateDatabaseWrapper());
+    database = databasePtr.get();
+  }
+
+  if (plugins.HasStorageArea())
+  {
+    LOG(WARNING) << "Using a custom storage area from plugins";
+    storage.reset(plugins.CreateStorageArea());
+  }
+  else
+  {
+    storage.reset(CreateStorageArea());
+  }
+
+  assert(database != NULL);
+  assert(storage.get() != NULL);
+
+  return ConfigureDatabase(*database, *storage, &plugins,
+                           upgradeDatabase, loadJobsFromDatabase);
+
+#elif ORTHANC_ENABLE_PLUGINS == 0
+  // The plugins are disabled
+
+  databasePtr.reset(CreateDatabaseWrapper());
+  storage.reset(CreateStorageArea());
+
+  assert(databasePtr.get() != NULL);
+  assert(storage.get() != NULL);
+
+  return ConfigureDatabase(*databasePtr, *storage, NULL,
+                           upgradeDatabase, loadJobsFromDatabase);
+
+#else
+#  error The macro ORTHANC_ENABLE_PLUGINS must be set to 0 or 1
+#endif
+}
+
+
+static bool StartOrthanc(int argc, 
+                         char* argv[],
+                         bool upgradeDatabase,
+                         bool loadJobsFromDatabase)
+{
+  return ConfigurePlugins(argc, argv, upgradeDatabase, loadJobsFromDatabase);
+}
+
+
+static bool DisplayPerformanceWarning()
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  LOG(WARNING) << "Performance warning: Non-release build, runtime debug assertions are turned on";
+  return true;
+}
+
+
+int main(int argc, char* argv[]) 
+{
+  Logging::Initialize();
+
+  bool upgradeDatabase = false;
+  bool loadJobsFromDatabase = true;
+  const char* configurationFile = NULL;
+
+
+  /**
+   * Parse the command-line options.
+   **/ 
+
+  for (int i = 1; i < argc; i++)
+  {
+    std::string argument(argv[i]); 
+
+    if (argument.empty())
+    {
+      // Ignore empty arguments
+    }
+    else if (argument[0] != '-')
+    {
+      if (configurationFile != NULL)
+      {
+        LOG(ERROR) << "More than one configuration path were provided on the command line, aborting";
+        return -1;
+      }
+      else
+      {
+        // Use the first argument that does not start with a "-" as
+        // the configuration file
+
+        // TODO WHAT IS THE ENCODING?
+        configurationFile = argv[i];
+      }
+    }
+    else if (argument == "--errors")
+    {
+      PrintErrors(argv[0]);
+      return 0;
+    }
+    else if (argument == "--help")
+    {
+      PrintHelp(argv[0]);
+      return 0;
+    }
+    else if (argument == "--version")
+    {
+      PrintVersion(argv[0]);
+      return 0;
+    }
+    else if (argument == "--verbose")
+    {
+      Logging::EnableInfoLevel(true);
+    }
+    else if (argument == "--trace")
+    {
+      Logging::EnableTraceLevel(true);
+    }
+    else if (boost::starts_with(argument, "--logdir="))
+    {
+      // TODO WHAT IS THE ENCODING?
+      std::string directory = argument.substr(9);
+
+      try
+      {
+        Logging::SetTargetFolder(directory);
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "The directory where to store the log files (" 
+                   << directory << ") is inexistent, aborting.";
+        return -1;
+      }
+    }
+    else if (boost::starts_with(argument, "--logfile="))
+    {
+      // TODO WHAT IS THE ENCODING?
+      std::string file = argument.substr(10);
+
+      try
+      {
+        Logging::SetTargetFile(file);
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "Cannot write to the specified log file (" 
+                   << file << "), aborting.";
+        return -1;
+      }
+    }
+    else if (argument == "--upgrade")
+    {
+      upgradeDatabase = true;
+    }
+    else if (argument == "--no-jobs")
+    {
+      loadJobsFromDatabase = false;
+    }
+    else if (boost::starts_with(argument, "--config="))
+    {
+      // TODO WHAT IS THE ENCODING?
+      std::string configurationSample;
+      GetFileResource(configurationSample, ServerResources::CONFIGURATION_SAMPLE);
+
+#if defined(_WIN32)
+      // Replace UNIX newlines with DOS newlines 
+      boost::replace_all(configurationSample, "\n", "\r\n");
+#endif
+
+      std::string target = argument.substr(9);
+
+      try
+      {
+        if (target == "-")
+        {
+          // New in 1.5.8: Print to stdout
+          std::cout << configurationSample;
+        }
+        else
+        {
+          SystemToolbox::WriteFile(configurationSample, target);
+        }
+        return 0;
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "Cannot write sample configuration as file \"" << target << "\"";
+        return -1;
+      }
+    }
+    else
+    {
+      LOG(WARNING) << "Option unsupported by the core of Orthanc: " << argument;
+    }
+  }
+
+
+  /**
+   * Launch Orthanc.
+   **/
+
+  {
+    std::string version(ORTHANC_VERSION);
+
+    if (std::string(ORTHANC_VERSION) == "mainline")
+    {
+      try
+      {
+        boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
+        std::time_t creation = boost::filesystem::last_write_time(exe);
+        boost::posix_time::ptime converted(boost::posix_time::from_time_t(creation));
+        version += " (" + boost::posix_time::to_iso_string(converted) + ")";
+      }
+      catch (...)
+      {
+      }
+    }
+
+    LOG(WARNING) << "Orthanc version: " << version;
+    assert(DisplayPerformanceWarning());
+
+    std::string s = "Architecture: ";
+    if (sizeof(void*) == 4)
+    {
+      s += "32-bit, ";
+    }
+    else if (sizeof(void*) == 8)
+    {
+      s += "64-bit, ";
+    }
+    else
+    {
+      s += "unsupported pointer size, ";
+    }
+
+    switch (Toolbox::DetectEndianness())
+    {
+      case Endianness_Little:
+        s += "little endian";
+        break;
+      
+      case Endianness_Big:
+        s += "big endian";
+        break;
+      
+      default:
+        s += "unsupported endianness";
+        break;
+    }
+    
+    LOG(INFO) << s;
+  }
+
+  int status = 0;
+  try
+  {
+    for (;;)
+    {
+      OrthancInitialize(configurationFile);
+
+      bool restart = StartOrthanc(argc, argv, upgradeDatabase, loadJobsFromDatabase);
+      if (restart)
+      {
+        OrthancFinalize();
+        LOG(WARNING) << "Logging system is resetting";
+        Logging::Reset();
+      }
+      else
+      {
+        break;
+      }
+    }
+  }
+  catch (const OrthancException& e)
+  {
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "] (code " << e.GetErrorCode() << ")";
+#if defined(_WIN32)
+    if (e.GetErrorCode() >= ErrorCode_START_PLUGINS)
+    {
+      status = static_cast<int>(ErrorCode_Plugin);
+    }
+    else
+    {
+      status = static_cast<int>(e.GetErrorCode());
+    }
+
+#else
+    status = -1;
+#endif
+  }
+  catch (const std::exception& e) 
+  {
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << e.what() << "]";
+    status = -1;
+  }
+  catch (const std::string& s) 
+  {
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << s << "]";
+    status = -1;
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Native exception, stopping now. Check your plugins, if any.";
+    status = -1;
+  }
+
+  LOG(WARNING) << "Orthanc has stopped";
+
+  OrthancFinalize();
+
+  return status;
+}