view OrthancFramework/Sources/RestApi/RestApiHierarchy.cpp @ 5802:816416425f2b Orthanc-1.12.4

fix unit tests
author Alain Mazy <am@orthanc.team>
date Fri, 20 Sep 2024 16:07:08 +0200
parents f7adfb22e20e
children
line wrap: on
line source

/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/


#include "../PrecompiledHeaders.h"
#include "RestApiHierarchy.h"

#include "../OrthancException.h"

#include <cassert>
#include <stdio.h>

namespace Orthanc
{
  RestApiHierarchy::Resource::Resource() : 
    getHandler_(NULL), 
    postHandler_(NULL),
    putHandler_(NULL), 
    deleteHandler_(NULL)
  {
  }


  bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const
  {
    switch (method)
    {
      case HttpMethod_Get:
        return getHandler_ != NULL;

      case HttpMethod_Post:
        return postHandler_ != NULL;

      case HttpMethod_Put:
        return putHandler_ != NULL;

      case HttpMethod_Delete:
        return deleteHandler_ != NULL;

      default:
        throw OrthancException(ErrorCode_ParameterOutOfRange);
    }
  }


  void RestApiHierarchy::Resource::Register(RestApiGetCall::Handler handler)
  {
    getHandler_ = handler;
  }

  void RestApiHierarchy::Resource::Register(RestApiPutCall::Handler handler)
  {
    putHandler_ = handler;
  }

  void RestApiHierarchy::Resource::Register(RestApiPostCall::Handler handler)
  {
    postHandler_ = handler;
  }

  void RestApiHierarchy::Resource::Register(RestApiDeleteCall::Handler handler)
  {
    deleteHandler_ = handler;
  }


  bool RestApiHierarchy::Resource::IsEmpty() const
  {
    return (getHandler_ == NULL &&
            postHandler_ == NULL &&
            putHandler_ == NULL &&
            deleteHandler_ == NULL);
  }


  RestApiHierarchy& RestApiHierarchy::AddChild(Children& children,
                                               const std::string& name)
  {
    Children::iterator it = children.find(name);

    if (it == children.end())
    {
      // Create new child
      RestApiHierarchy *child = new RestApiHierarchy;
      children[name] = child;
      return *child;
    }
    else
    {
      return *it->second;
    }
  }



  bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const
  {
    if (getHandler_ != NULL)
    {
      getHandler_(call);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const
  {
    if (putHandler_ != NULL)
    {
      putHandler_(call);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const
  {
    if (postHandler_ != NULL)
    {
      postHandler_(call);
      return true;
    }
    else
    {
      return false;
    }
  }


  bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const
  {
    if (deleteHandler_ != NULL)
    {
      deleteHandler_(call);
      return true;
    }
    else
    {
      return false;
    }
  }


  void RestApiHierarchy::DeleteChildren(Children& children)
  {
    for (Children::iterator it = children.begin();
         it != children.end(); ++it)
    {
      delete it->second;
    }
  }


  template <typename Handler>
  void RestApiHierarchy::RegisterInternal(const RestApiPath& path,
                                          Handler handler,
                                          size_t level)
  {
    if (path.GetLevelCount() == level)
    {
      if (path.IsUniversalTrailing())
      {
        handlersWithTrailing_.Register(handler);
      }
      else
      {
        handlers_.Register(handler);
      }
    }
    else
    {
      RestApiHierarchy* child;
      if (path.IsWildcardLevel(level))
      {
        child = &AddChild(wildcardChildren_, path.GetWildcardName(level));
      }
      else
      {
        child = &AddChild(children_, path.GetLevelName(level));
      }

      child->RegisterInternal(path, handler, level + 1);
    }
  }


  bool RestApiHierarchy::LookupResource(HttpToolbox::Arguments& components,
                                        const UriComponents& uri,
                                        IVisitor& visitor,
                                        size_t level)
  {
    if (uri.size() != 0 &&
        level > uri.size())
    {
      return false;
    }

    // Look for an exact match on the resource of interest
    if (uri.size() == 0 ||
        level == uri.size())
    {
      UriComponents noTrailing;

      if (!handlers_.IsEmpty() &&
          visitor.Visit(handlers_, uri, false, components, noTrailing))
      {
        return true;
      }
    }


    if (level < uri.size())  // A recursive call is possible
    {
      // Try and go down in the hierarchy, using an exact match for the child
      Children::const_iterator child = children_.find(uri[level]);
      if (child != children_.end())
      {
        if (child->second->LookupResource(components, uri, visitor, level + 1))
        {
          return true;
        }
      }

      // Try and go down in the hierarchy, using wildcard rules for children
      for (child = wildcardChildren_.begin();
           child != wildcardChildren_.end(); ++child)
      {
        HttpToolbox::Arguments subComponents = components;
        subComponents[child->first] = uri[level];

        if (child->second->LookupResource(subComponents, uri, visitor, level + 1))
        {
          return true;
        }        
      }
    }


    // As a last resort, call the universal handlers, if any
    if (!handlersWithTrailing_.IsEmpty())
    {
      UriComponents trailing;
      trailing.resize(uri.size() - level);
      size_t pos = 0;
      for (size_t i = level; i < uri.size(); i++, pos++)
      {
        trailing[pos] = uri[i];
      }

      assert(pos == trailing.size());

      if (visitor.Visit(handlersWithTrailing_, uri, true, components, trailing))
      {
        return true;
      }
    }

    return false;
  }


  bool RestApiHierarchy::CanGenerateDirectory() const
  {
    return (handlersWithTrailing_.IsEmpty() &&
            wildcardChildren_.empty());
  }


  bool RestApiHierarchy::GetDirectory(Json::Value& result,
                                      const UriComponents& uri,
                                      size_t level)
  {
    if (uri.size() == level)
    {
      if (CanGenerateDirectory())
      {
        result = Json::arrayValue;

        for (Children::const_iterator it = children_.begin();
             it != children_.end(); ++it)
        {
          result.append(it->first);
        }

        return true;
      }
      else
      {
        return false;
      }
    }

    Children::const_iterator child = children_.find(uri[level]);
    if (child != children_.end())
    {
      if (child->second->GetDirectory(result, uri, level + 1))
      {
        return true;
      }
    }

    for (child = wildcardChildren_.begin(); 
         child != wildcardChildren_.end(); ++child)
    {
      if (child->second->GetDirectory(result, uri, level + 1))
      {
        return true;
      }
    }

    return false;
  }
                       

  RestApiHierarchy::~RestApiHierarchy()
  {
    DeleteChildren(children_);
    DeleteChildren(wildcardChildren_);
  }

  void RestApiHierarchy::Register(const std::string& uri,
                                  RestApiGetCall::Handler handler)
  {
    RestApiPath path(uri);
    RegisterInternal(path, handler, 0);
  }

  void RestApiHierarchy::Register(const std::string& uri,
                                  RestApiPutCall::Handler handler)
  {
    RestApiPath path(uri);
    RegisterInternal(path, handler, 0);
  }

  void RestApiHierarchy::Register(const std::string& uri,
                                  RestApiPostCall::Handler handler)
  {
    RestApiPath path(uri);
    RegisterInternal(path, handler, 0);
  }

  void RestApiHierarchy::Register(const std::string& uri,
                                  RestApiDeleteCall::Handler handler)
  {
    RestApiPath path(uri);
    RegisterInternal(path, handler, 0);
  }

  void RestApiHierarchy::CreateSiteMap(Json::Value& target) const
  {
    target = Json::objectValue;

    /*std::string s = " ";
      if (handlers_.HasHandler(HttpMethod_Get))
      {
      s += "GET ";
      }

      if (handlers_.HasHandler(HttpMethod_Post))
      {
      s += "POST ";
      }

      if (handlers_.HasHandler(HttpMethod_Put))
      {
      s += "PUT ";
      }

      if (handlers_.HasHandler(HttpMethod_Delete))
      {
      s += "DELETE ";
      }

      target = s;*/
      
    for (Children::const_iterator it = children_.begin();
         it != children_.end(); ++it)
    {
      it->second->CreateSiteMap(target[it->first]);
    }
      
    for (Children::const_iterator it = wildcardChildren_.begin();
         it != wildcardChildren_.end(); ++it)
    {
      it->second->CreateSiteMap(target["<" + it->first + ">"]);
    }
  }

  bool RestApiHierarchy::GetDirectory(Json::Value &result, const UriComponents &uri)
  {
    return GetDirectory(result, uri, 0);
  }


  bool RestApiHierarchy::LookupResource(const UriComponents& uri,
                                        IVisitor& visitor)
  {
    HttpToolbox::Arguments components;
    return LookupResource(components, uri, visitor, 0);
  }    



  namespace
  {
    // Anonymous namespace to avoid clashes between compilation modules

    class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor
    {
    private:
      std::set<HttpMethod>& methods_;

    public:
      explicit AcceptedMethodsVisitor(std::set<HttpMethod>& methods) :
        methods_(methods)
      {
      }

      virtual bool Visit(const RestApiHierarchy::Resource& resource,
                         const UriComponents& uri,
                         bool hasTrailing,
                         const HttpToolbox::Arguments& components,
                         const UriComponents& trailing)
      {
        if (!hasTrailing)  // Ignore universal handlers
        {
          if (resource.HasHandler(HttpMethod_Get))
          {
            methods_.insert(HttpMethod_Get);
          }

          if (resource.HasHandler(HttpMethod_Post))
          {
            methods_.insert(HttpMethod_Post);
          }

          if (resource.HasHandler(HttpMethod_Put))
          {
            methods_.insert(HttpMethod_Put);
          }

          if (resource.HasHandler(HttpMethod_Delete))
          {
            methods_.insert(HttpMethod_Delete);
          }
        }

        return false;  // Continue to check all the possible ways to access this URI
      }
    };
  }

  void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods,
                                            const UriComponents& uri)
  {
    HttpToolbox::Arguments components;
    AcceptedMethodsVisitor visitor(methods);
    if (LookupResource(components, uri, visitor, 0))
    {
      Json::Value d;
      if (GetDirectory(d, uri))
      {
        methods.insert(HttpMethod_Get);
      }
    }
  }

  void RestApiHierarchy::ExploreAllResources(IVisitor& visitor,
                                             const UriComponents& path,
                                             const std::set<std::string>& uriArguments) const
  {
    HttpToolbox::Arguments args;

    for (std::set<std::string>::const_iterator it = uriArguments.begin(); it != uriArguments.end(); ++it)
    {
      args[*it] = "";
    }
    
    if (!handlers_.IsEmpty())
    {
      visitor.Visit(handlers_, path, false, args, UriComponents());
    }

    if (!handlersWithTrailing_.IsEmpty())
    {
      visitor.Visit(handlersWithTrailing_, path, true, args, UriComponents());
    }
    
    for (Children::const_iterator
           it = children_.begin(); it != children_.end(); ++it)
    {
      assert(it->second != NULL);
      UriComponents c = path;
      c.push_back(it->first);
      it->second->ExploreAllResources(visitor, c, uriArguments);
    }
    
    for (Children::const_iterator
           it = wildcardChildren_.begin(); it != wildcardChildren_.end(); ++it)
    {
      if (uriArguments.find(it->first) != uriArguments.end())
      {
        throw OrthancException(ErrorCode_InternalError, "Twice the same URI argument in a path: " + it->first);
      }

      std::set<std::string> d = uriArguments;
      d.insert(it->first);
      
      assert(it->second != NULL);
      UriComponents c = path;
      c.push_back("{" + it->first + "}");
      it->second->ExploreAllResources(visitor, c, d);
    }
  }
}