From 1e0d830529c8cb05c69cda22403e5fd2d254e5f6 Mon Sep 17 00:00:00 2001 From: David Marteau Date: Wed, 7 Dec 2016 22:09:57 +0100 Subject: [PATCH] Server refactoring: implements service modules registry Implementation for https://github.com/qgis/QGIS-Enhancement-Proposals/issues/74 --- Testing/Temporary/CTestCostData.txt | 1 + python/server/qgsserverrequest.sip | 76 +++++++++++ python/server/qgsserverresponse.sip | 77 +++++++++++ python/server/qgsservice.sip | 60 +++++++++ python/server/qgsservicemodule.sip | 50 +++++++ python/server/qgsserviceregistry.sip | 83 ++++++++++++ python/server/server.sip | 7 + src/server/CMakeLists.txt | 10 ++ src/server/qgsserverrequest.cpp | 58 ++++++++ src/server/qgsserverrequest.h | 83 ++++++++++++ src/server/qgsserverresponse.cpp | 82 ++++++++++++ src/server/qgsserverresponse.h | 86 ++++++++++++ src/server/qgsservice.cpp | 35 +++++ src/server/qgsservice.h | 64 +++++++++ src/server/qgsserviceloader.cpp | 34 +++++ src/server/qgsserviceloader.h | 57 ++++++++ src/server/qgsservicemodule.cpp | 36 +++++ src/server/qgsservicemodule.h | 54 ++++++++ src/server/qgsservicenativeloader.cpp | 168 +++++++++++++++++++++++ src/server/qgsservicenativeloader.h | 90 +++++++++++++ src/server/qgsservicepythonloader.cpp | 82 ++++++++++++ src/server/qgsservicepythonloader.h | 73 ++++++++++ src/server/qgsserviceregistry.cpp | 185 ++++++++++++++++++++++++++ src/server/qgsserviceregistry.h | 105 +++++++++++++++ tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_services.py | 89 +++++++++++++ 26 files changed, 1746 insertions(+) create mode 100644 Testing/Temporary/CTestCostData.txt create mode 100644 python/server/qgsserverrequest.sip create mode 100644 python/server/qgsserverresponse.sip create mode 100644 python/server/qgsservice.sip create mode 100644 python/server/qgsservicemodule.sip create mode 100644 python/server/qgsserviceregistry.sip create mode 100644 src/server/qgsserverrequest.cpp create mode 100644 src/server/qgsserverrequest.h create mode 100644 src/server/qgsserverresponse.cpp create mode 100644 src/server/qgsserverresponse.h create mode 100644 src/server/qgsservice.cpp create mode 100644 src/server/qgsservice.h create mode 100644 src/server/qgsserviceloader.cpp create mode 100644 src/server/qgsserviceloader.h create mode 100644 src/server/qgsservicemodule.cpp create mode 100644 src/server/qgsservicemodule.h create mode 100644 src/server/qgsservicenativeloader.cpp create mode 100644 src/server/qgsservicenativeloader.h create mode 100644 src/server/qgsservicepythonloader.cpp create mode 100644 src/server/qgsservicepythonloader.h create mode 100644 src/server/qgsserviceregistry.cpp create mode 100644 src/server/qgsserviceregistry.h create mode 100644 tests/src/python/test_services.py diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 00000000000..ed97d539c09 --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/python/server/qgsserverrequest.sip b/python/server/qgsserverrequest.sip new file mode 100644 index 00000000000..4c1ba991be0 --- /dev/null +++ b/python/server/qgsserverrequest.sip @@ -0,0 +1,76 @@ +/*************************************************************************** + qgsserverrequest.h + + Define ruquest class for getting request contents + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/** + * \ingroup server + * QgsServerRequest + * Class defining request intreface passed to services QgsService::executeRequest() method + * + * Note about design: this intreface must be passed along to python and thus signatures methods must be + * compatible with pyQGS/pyQT api and rules. + */ +class QgsServerRequest +{ +%TypeHeaderCode +#include "qgsserverrequest.h" +%End + public: + + enum Method { + HeadMethod, PutMethod, GetMethod, PostMethod, DeleteMethod + }; + + /** + * Constructor + * + * @param url the lurl string + * @param method the request method + */ + QgsServerRequest( const QString& url, Method method ); + + /** + * Constructor + * + * @param url QUrl + * @param method the rquest method + */ + QgsServerRequest( const QUrl& url, Method method ); + + //! destructor + virtual ~QgsServerRequest(); + + /** + * @return the request url + */ + virtual const QUrl& url() const; + + /** + * @return the rquest method + */ + virtual Method method() const; + + /** + * Return post/put data + * The default implementation retfurn nullptr + * @return a QByteArray pointer or nullptr + */ + virtual const QByteArray* data() const; + +}; + diff --git a/python/server/qgsserverresponse.sip b/python/server/qgsserverresponse.sip new file mode 100644 index 00000000000..71822f90889 --- /dev/null +++ b/python/server/qgsserverresponse.sip @@ -0,0 +1,77 @@ +/*************************************************************************** + qgsserverresponse.h + + Define response class for services + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/** + * \ingroup server + * QgsServerResponse + * Class defining response interface passed to services QgsService::executeRequest() method + * + * Note: + * This class is intended to be used from python code: method signatures and return types should be + * compatible with pyQGIS/pyQT types and rules. + * + */ +class QgsServerResponse +{ +%TypeHeaderCode +#include "qgsserverresponse.h" +%End + public: + + //!constructor + QgsServerResponse(); + + //! destructor + virtual ~QgsServerResponse(); + + /** Set header entry + * Add header entry to the response + * Note that it is usually an error to set hedaer after writng data + */ + virtual void setHeader( const QString& key, const QString& value ) = 0; + + /** Set the http return code + */ + virtual void setReturnCode( int code ) = 0; + + /** + * Send error + */ + virtual void sendError( int code, const QString& message ) = 0; + + /** + * Write string + */ + virtual void write(const QString& data ); + + /** + * Write chunk af data + * They are convenience method that will write directly to the + * underlying I/O device + */ + virtual qint64 write(const QByteArray& byteArray ); + + + /** + * Return the underlying QIODevice + */ + virtual QIODevice* io() = 0; + +}; + diff --git a/python/server/qgsservice.sip b/python/server/qgsservice.sip new file mode 100644 index 00000000000..155a5542087 --- /dev/null +++ b/python/server/qgsservice.sip @@ -0,0 +1,60 @@ +/*************************************************************************** + qgsservice.h + + Class defining the service interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/** + * \ingroup server + * QgsService + * Class defining interfaces for QGIS server services + * + * This class provides methods for executing server requests + * They are registered at runtime for a given service name. + * + */ +class QgsService +{ +%TypeHeaderCode +#include "qgsservice.h" +#include "qgsserverrequest.h" +#include "qgsserverresponse.h" +%End + + public: + + //! Constructor + QgsService(); + + //! Destructor + virtual ~QgsService(); + + /** + * Return true if the given method is supported for that + * service. + * @param method QGSMethodType the + * @return QString containing the configuration file path + */ + virtual bool allowMethod( QgsServerRequest::Method ) const = 0; + + /** + * Execute the requests and set result in QgsServerRequest + * @param request a QgsServerRequest instance + * @param response a QgsServerResponse instance + */ + virtual void executeRequest( const QgsServerRequest& request, QgsServerResponse& response ) = 0; +}; + diff --git a/python/server/qgsservicemodule.sip b/python/server/qgsservicemodule.sip new file mode 100644 index 00000000000..781629db9b5 --- /dev/null +++ b/python/server/qgsservicemodule.sip @@ -0,0 +1,50 @@ +/*************************************************************************** + qgsservicemodule.h + + Class defining the service module interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/** + * \ingroup server + * QgsServiceModule + * Class defining the service module interface for QGIS server services + * + * This class act as a service registrar for services. + * + * For dynamic modules, a QgsServiceModule instance is returned from the QGS_ServiceModule_Init() entry point + */ +class QgsServiceModule +{ +%TypeHeaderCode +#include "qgsservicemodule.h" +#include "qgsserviceregistry.h" +%End + public: + + //! Constructor + QgsServiceModule(); + + //! Destructor + virtual ~QgsServiceModule() = 0; + + /** + * Ask module to registe all provided services + * @param registry QgsServiceRegistry + */ + virtual void registerSelf( QgsServiceRegistry& registry ) = 0; +}; + + diff --git a/python/server/qgsserviceregistry.sip b/python/server/qgsserviceregistry.sip new file mode 100644 index 00000000000..ed61c9f10b9 --- /dev/null +++ b/python/server/qgsserviceregistry.sip @@ -0,0 +1,83 @@ +/*************************************************************************** + qgsserviceregistry.h + + Class defining the service manager for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +/** + * \ingroup server + * QgsServiceRegistry + * Class defining the cegistry manager for QGIS server services + * + * This class provides methods for registering and retrieving + * services. Note that the regstiry does not hord ownership of + * registered service but vill call the 'release' method on cleanup) + * + */ +class QgsServiceRegistry +{ +%TypeHeaderCode +#include "qgsserviceregistry.h" +#include "qgsservice.h" +#include "qgsserverrequest.h" +#include "qgsserverresponse.h" +%End + public: + + //! Constructor + QgsServiceRegistry(); + + //! Destructor + ~QgsServiceRegistry(); + + /** + * Retrieve a service from its name + * @param name the name of the service + * @param version the required version (optional) + * @return QgsService + */ + QgsService* getService( const QString& name, const QString& version = QString() ); + + /** + * Register a service by its name + * + * This method is intended to be called by modules for registering + * services. A module may register multiple services. + * The registry gain ownership of services. + * + * @param name the name of the service + * @param service a QgsServerResponse to be registered + * @param version the service version + */ + void registerService( const QString& name, QgsService* service /Transfer/, const QString& version ); + + /** + * Initialize registry, load modules and auto register services + * @param nativeModulepath the native module path + * @param pythonModulePath the python module path + * + * If pythonModulePath is not specified the environnement variables QGIS_PYTHON_SERVICE_PATH + * is examined. + */ + void init( const QString& nativeModulepath, const QString& pythonModulePath = QString() ); + + /** + * Clean up registered service and unregister modules + */ + void cleanUp(); +}; + + diff --git a/python/server/server.sip b/python/server/server.sip index 537cd0b8657..625de7d9a1f 100644 --- a/python/server/server.sip +++ b/python/server/server.sip @@ -28,3 +28,10 @@ %Include qgswfsprojectparser.sip %Include qgsconfigcache.sip %Include qgsserver.sip + +%Include qgsserverrequest.sip +%Include qgsserverresponse.sip +%Include qgsservice.sip +%Include qgsservicemodule.sip +%Include qgsserviceregistry.sip + diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 44955c07b8a..a3379c8d032 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -49,6 +49,16 @@ SET ( qgis_mapserv_SRCS qgssldconfigparser.cpp qgsconfigparserutils.cpp qgsserver.cpp +#XXX https://github.com/qgis/QGIS-Enhancement-Proposals/issues/74 + qgsservice.cpp + qgsservicemodule.cpp + qgsserviceloader.cpp + qgsservicenativeloader.cpp + qgsservicepythonloader.cpp + qgsserviceregistry.cpp + qgsserverrequest.cpp + qgsserverresponse.cpp +#---------------------------- ) IF("${Qt5Network_VERSION}" VERSION_LESS "5.0.0") SET (qgis_mapserv_SRCS ${qgis_mapserv_SRCS} diff --git a/src/server/qgsserverrequest.cpp b/src/server/qgsserverrequest.cpp new file mode 100644 index 00000000000..afbec2f9c95 --- /dev/null +++ b/src/server/qgsserverrequest.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** + qgsserverrequest.cpp + + Define ruquest class for getting request contents + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsserverrequest.h" + + +QgsServerRequest::QgsServerRequest( const QString& url, Method method ) +: mUrl(url) +, mMethod(method) +{ + +} + +QgsServerRequest::QgsServerRequest( const QUrl& url, Method method ) +: mUrl(url) +, mMethod(method) +{ + +} + +//! destructor +QgsServerRequest::~QgsServerRequest() +{ + +} + +const QUrl& QgsServerRequest::url() const +{ + return mUrl; +} + +QgsServerRequest::Method QgsServerRequest::method() const +{ + return mMethod; +} + +const QByteArray* QgsServerRequest::data() const +{ + return nullptr; +} + + diff --git a/src/server/qgsserverrequest.h b/src/server/qgsserverrequest.h new file mode 100644 index 00000000000..cefce31806b --- /dev/null +++ b/src/server/qgsserverrequest.h @@ -0,0 +1,83 @@ +/*************************************************************************** + qgsserverrequest.h + + Define ruquest class for getting request contents + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVERREQUEST_H +#define QGSSERVERREQUEST_H + +#include + +/** + * \ingroup server + * QgsServerRequest + * Class defining request intreface passed to services QgsService::executeRequest() method + * + * Note about design: this intreface must be passed along to python and thus signatures methods must be + * compatible with pyQGS/pyQT api and rules. + */ +class SERVER_EXPORT QgsServerRequest +{ + public: + + enum Method { + HeadMethod, PutMethod, GetMethod, PostMethod, DeleteMethod + }; + + /** + * Constructor + * + * @param url the lurl string + * @param method the request method + */ + QgsServerRequest( const QString& url, Method method ); + + /** + * Constructor + * + * @param url QUrl + * @param method the rquest method + */ + QgsServerRequest( const QUrl& url, Method method ); + + //! destructor + virtual ~QgsServerRequest(); + + /** + * @return the request url + */ + virtual const QUrl& url() const; + + /** + * @return the rquest method + */ + virtual Method method() const; + + /** + * Return post/put data + * The default implementation retfurn nullptr + * @return a QByteArray pointer or nullptr + */ + virtual const QByteArray* data() const; + + protected: + + QUrl mUrl; + Method mMethod; + +}; + +#endif diff --git a/src/server/qgsserverresponse.cpp b/src/server/qgsserverresponse.cpp new file mode 100644 index 00000000000..7fb2566a8f5 --- /dev/null +++ b/src/server/qgsserverresponse.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + qgsserverresponse.h + + Define response class for services + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsserverresponse.h" +#include "qgsmessagelog.h" +#include + +//! constructor +QgsServerResponse::QgsServerResponse() +{ + +} + + +//! destructor +QgsServerResponse::~QgsServerResponse() +{ + +} + +void QgsServerResponse::write( const QString& data ) +{ + QIODevice* iodev = io(); + if( iodev ) + { + QTextStream stream(iodev); + stream << data; + } + else + { + QgsMessageLog::logMessage("Error: No IODevice in QgsServerResponse !!!"); + } +} + + +qint64 QgsServerResponse::write( const QByteArray& byteArray ) +{ + QIODevice* iodev = io(); + if( iodev ) + { + return iodev->write(byteArray); + } + return 0; +} + + +qint64 QgsServerResponse::write( const char* data, qint64 maxsize ) +{ + QIODevice* iodev = io(); + if( iodev ) + { + return iodev->write(data, maxsize); + } + return 0; +} + +qint64 QgsServerResponse::write( const char* data ) +{ + QIODevice* iodev = io(); + if( iodev ) + { + return iodev->write(data); + } + return 0; +} + diff --git a/src/server/qgsserverresponse.h b/src/server/qgsserverresponse.h new file mode 100644 index 00000000000..932044a6dac --- /dev/null +++ b/src/server/qgsserverresponse.h @@ -0,0 +1,86 @@ +/*************************************************************************** + qgsserverresponse.h + + Define response class for services + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVERRESPONSE_H +#define QGSSERVERRESPONSE_H + +#include +#include + +/** + * \ingroup server + * QgsServerResponse + * Class defining response interface passed to services QgsService::executeRequest() method + * + * Note: + * This class is intended to be used from python code: method signatures and return types should be + * compatible with pyQGIS/pyQT types and rules. + * + */ +class SERVER_EXPORT QgsServerResponse +{ + public: + + //!constructor + QgsServerResponse(); + + //! destructor + virtual ~QgsServerResponse(); + + /** Set header entry + * Add header entry to the response + * Note that it is usually an error to set hedaer after writng data + */ + virtual void setHeader( const QString& key, const QString& value ) = 0; + + /** Set the http return code + */ + virtual void setReturnCode( int code ) = 0; + + /** + * Send error + */ + virtual void sendError( int code, const QString& message ) = 0; + + /** + * Write string + * @param data string to write + */ + virtual void write( const QString& data ); + + /** + * Write chunk af data + * They are convenience method that will write directly to the + * underlying I/O device + */ + virtual qint64 write( const QByteArray &byteArray ); + + // Not exposed in python + virtual qint64 write( const char* data, qint64 maxsize); + + // Not exposed in python + virtual qint64 write( const char* data ); + + /** + * Return the underlying QIODevice + */ + virtual QIODevice* io() = 0; + +}; + +#endif diff --git a/src/server/qgsservice.cpp b/src/server/qgsservice.cpp new file mode 100644 index 00000000000..523d21586ed --- /dev/null +++ b/src/server/qgsservice.cpp @@ -0,0 +1,35 @@ +/*************************************************************************** + qgsservice.cpp + + Class defining the service interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include "qgsservice.h" + +//! Constructor +QgsService::QgsService() +{ + +} + +//! Destructor +QgsService::~QgsService() +{ + +} + + diff --git a/src/server/qgsservice.h b/src/server/qgsservice.h new file mode 100644 index 00000000000..c852cf701c3 --- /dev/null +++ b/src/server/qgsservice.h @@ -0,0 +1,64 @@ +/*************************************************************************** + qgsservice.h + + Class defining the service interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef QGSSERVICECOMPONENT_H +#define QGSSERVICECOMPONENT_H + +#include "qgsserverrequest.h" +#include "qgsserverresponse.h" + +/** + * \ingroup server + * QgsService + * Class defining interfaces for QGIS server services + * + * This class provides methods for executing server requests + * They are registered at runtime for a given service name. + * + */ +class SERVER_EXPORT QgsService +{ + + public: + + //! Constructor + QgsService(); + + //! Destructor + virtual ~QgsService(); + + /** + * Return true if the given method is supported for that + * service. + * @param method QGSMethodType the + * @return QString containing the configuration file path + */ + virtual bool allowMethod( QgsServerRequest::Method ) const = 0; + + /** + * Execute the requests and set result in QgsServerRequest + * @param request a QgsServerRequest instance + * @param response a QgsServerResponse instance + */ + virtual void executeRequest( const QgsServerRequest& request, QgsServerResponse& response ) = 0; +}; + +#endif + diff --git a/src/server/qgsserviceloader.cpp b/src/server/qgsserviceloader.cpp new file mode 100644 index 00000000000..fd3135318a1 --- /dev/null +++ b/src/server/qgsserviceloader.cpp @@ -0,0 +1,34 @@ +/*************************************************************************** + qgsserviceloader.cpp + + Define abstract loader class for service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsserviceloader.h" + +//! Constructor +QgsServiceLoader::QgsServiceLoader() +{ + +} + +//! Destructor +QgsServiceLoader::~QgsServiceLoader() +{ + +} + + diff --git a/src/server/qgsserviceloader.h b/src/server/qgsserviceloader.h new file mode 100644 index 00000000000..8ccb258e727 --- /dev/null +++ b/src/server/qgsserviceloader.h @@ -0,0 +1,57 @@ +/*************************************************************************** + qgsserviceloader.h + + Define abstract loader class for service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVICELOADER_H +#define QGSSERVICELOADER_H + +#include + +class QgsServiceModule; +class QgsServiceRegistry; + +/** + * \ingroup server + * QgsServiceLoader + * Abstract base Class defining the native service module loader for QGIS server services + */ +class SERVER_EXPORT QgsServiceLoader +{ + public: + + //! Constructor (required by SIP bindings ???) + QgsServiceLoader(); + + //! Destructor + virtual ~QgsServiceLoader() = 0; + + /** + * Lead all medules from path + * @param modulePath the path to look for module + * @param registrar QgsServiceRegistry instance for registering services + */ + virtual void loadModules( const QString& modulePath, QgsServiceRegistry& registrar ) = 0; + + /** + * Unload all modules + */ + virtual void unloadModules() = 0; +}; + +#endif + + diff --git a/src/server/qgsservicemodule.cpp b/src/server/qgsservicemodule.cpp new file mode 100644 index 00000000000..95c870de364 --- /dev/null +++ b/src/server/qgsservicemodule.cpp @@ -0,0 +1,36 @@ +/*************************************************************************** + qgsservicemodule.h + + Class defining the service module interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + + +#include "qgsservicemodule.h" + +//! Constructor +QgsServiceModule::QgsServiceModule() +{ + +} + +//! Destructor +QgsServiceModule::~QgsServiceModule() +{ + +} + + diff --git a/src/server/qgsservicemodule.h b/src/server/qgsservicemodule.h new file mode 100644 index 00000000000..f5f7fd0a247 --- /dev/null +++ b/src/server/qgsservicemodule.h @@ -0,0 +1,54 @@ +/*************************************************************************** + qgsservicemodule.h + + Class defining the service module interface for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef QGSSERVICEMODULE_H +#define QGSSERVICEMODULE_H + +class QgsServiceRegistry; + +/** + * \ingroup server + * QgsServiceModule + * Class defining the service module interface for QGIS server services + * + * This class act as a service registrar for services. + * + * For dynamic modules, a QgsServiceModule instance is returned from the QGS_ServiceModule_Init() entry point + */ +class SERVER_EXPORT QgsServiceModule +{ + + public: + + //! Constructor + QgsServiceModule(); + + //! Destructor + virtual ~QgsServiceModule() = 0; + + /** + * Ask module to register all provided services + * @param registry QgsServiceRegistry + */ + virtual void registerSelf( QgsServiceRegistry& registry ) = 0; +}; + +#endif + diff --git a/src/server/qgsservicenativeloader.cpp b/src/server/qgsservicenativeloader.cpp new file mode 100644 index 00000000000..47436c314a9 --- /dev/null +++ b/src/server/qgsservicenativeloader.cpp @@ -0,0 +1,168 @@ +/*************************************************************************** + qgsservicerenativeloader.cpp + + Define Loader for native service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include + +#include "qgsservicenativeloader.h" +#include "qgsserviceregistry.h" +#include "qgsservicemodule.h" +#include "qgslogger.h" +#include "qgsmessagelog.h" +#include "qgis.h" + + +typedef void unloadHook_t( QgsServiceModule* ); + +class QgsServiceNativeModuleEntry +{ + public: + QgsServiceNativeModuleEntry( const QString& location ) + : mLocation(location) + , mModule(nullptr) + {} + + QString mLocation; + QgsServiceModule* mModule; + unloadHook_t* mUnloadHook; +}; + +//! Constructor +QgsServiceNativeLoader::QgsServiceNativeLoader() +{ +} + +//! Destructor +QgsServiceNativeLoader::~QgsServiceNativeLoader() +{ + +} + +void QgsServiceNativeLoader::loadModules( const QString& modulePath, QgsServiceRegistry& registrar ) +{ + QDir moduleDir(modulePath); + moduleDir.setSorting( QDir::Name | QDir::IgnoreCase ); + moduleDir.setFilter( QDir::Files ); + +#if defined(Q_OS_WIN) || defined(__CYGWIN__) + moduleDir.setNameFilters( QStringList( "*.dll" ) ); +#else + moduleDir.setNameFilters( QStringList( "*.so" ) ); +#endif + + QgsDebugMsg( QString( "Checking %1 for native services modules" ).arg( moduleDir.path() ) ); + + Q_FOREACH( const QFileInfo& fi, moduleDir.entryInfoList() ) + { + QgsServiceModule* module = loadNativeModule( fi.filePath() ); + if( module ) + { + // Register services + module->registerSelf( registrar ); + } + } +} + + +typedef QgsServiceModule* serviceEntryPoint_t(); + +QgsServiceModule* QgsServiceNativeLoader::loadNativeModule( const QString& location ) +{ + QgsServiceNativeModuleEntry* entry = findModuleEntry( location ); + if( entry ) + { + return entry->mModule; + } + + QLibrary lib( location ); + QgsDebugMsg( QString("Loading native module %1").arg(location) ); + if( !lib.load() ) + { + QgsMessageLog::logMessage( QString("Failed to load library %1: %2").arg(lib.fileName(), lib.errorString()) ); + return nullptr; + } + // Load entry point + serviceEntryPoint_t* + entryPointFunc = reinterpret_cast( cast_to_fptr( lib.resolve("QGS_ServiceModule_Init") )); + + if( entryPointFunc ) + { + QgsServiceModule* module = entryPointFunc(); + if( module ) + { + entry = new QgsServiceNativeModuleEntry( location ); + entry->mModule = module; + entry->mUnloadHook = reinterpret_cast( cast_to_fptr( lib.resolve("QGS_ServiceModule_Exit") )); + + // Add entry + mModules.insert( location, ModuleTable::mapped_type(entry) ); + return module; + } + else + { + QgsMessageLog::logMessage( QString("No entry point for module %1").arg(lib.fileName()) ); + } + } + else + { + QgsMessageLog::logMessage( QString("Error: entry point returned null for %1").arg(lib.fileName()) ); + } + + // No module found: release library + lib.unload(); + return nullptr; +} + +void QgsServiceNativeLoader::unloadModules() +{ + ModuleTable::iterator it = mModules.begin(); + ModuleTable::iterator end = mModules.end(); + + while( it!=end ) + { + unloadModuleEntry( it->get() ); + ++it; + } + + mModules.clear(); +} + +QgsServiceNativeModuleEntry* QgsServiceNativeLoader::findModuleEntry( const QString& location ) +{ + QgsServiceNativeModuleEntry* entry = nullptr; + ModuleTable::iterator item = mModules.find(location); + if( item != mModules.end() ) + { + entry = item->get(); + } + return entry; +} + +void QgsServiceNativeLoader::unloadModuleEntry( QgsServiceNativeModuleEntry* entry ) +{ + // Call cleanup function if it exists + if( entry->mUnloadHook ) { + entry->mUnloadHook( entry->mModule ); + } + + QLibrary lib( entry->mLocation ); + lib.unload(); +} + + diff --git a/src/server/qgsservicenativeloader.h b/src/server/qgsservicenativeloader.h new file mode 100644 index 00000000000..6c28fc0a15a --- /dev/null +++ b/src/server/qgsservicenativeloader.h @@ -0,0 +1,90 @@ +/*************************************************************************** + qgsservicerenativeloader.h + + Define Loader for native service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVICENATIVELOADER_H +#define QGSSERVICENATIVELOADER_H + +class QgsServiceModule; +class QgsServiceRegistry; +class QgsServiceNativeModuleEntry; + +#include "qgsserviceloader.h" +#include + +#include + +/** + * \ingroup server + * QgsServiceNativeLoader + * Class defining the native service module loader for QGIS server services + * + * This class provides methods for loading and managing hook for native (C++) modules + * + */ +class SERVER_EXPORT QgsServiceNativeLoader: public QgsServiceLoader +{ + public: + + //! Constructor + QgsServiceNativeLoader(); + + //! Destructor + ~QgsServiceNativeLoader(); + + /** + * Lead all medules from path + * @param modulePath the path to look for module + * @parama registrar QgsServiceRegistry instance for registering services + */ + void loadModules( const QString& modulePath, QgsServiceRegistry& registrar ) override; + + /** + * Unload all modules + */ + void unloadModules() override; + + /** + * Load the native module from path + * + * @param location QString location holding the module relalive path + * @return a qgsservicemodule instance + */ + QgsServiceModule* loadNativeModule( const QString& location ); + + + private: + typedef QHash > ModuleTable; + + /** + * Find module + * @param path the module path + * @return a module hook entry + */ + QgsServiceNativeModuleEntry* findModuleEntry( const QString& path ); + + /** + * Unload medule hook + */ + void unloadModuleEntry( QgsServiceNativeModuleEntry* entry ); + + //! Associative storage for module handles + ModuleTable mModules; +}; + +#endif + diff --git a/src/server/qgsservicepythonloader.cpp b/src/server/qgsservicepythonloader.cpp new file mode 100644 index 00000000000..bfd8be0e1dc --- /dev/null +++ b/src/server/qgsservicepythonloader.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + qgsservicerenativeloader.cpp + + Define Loader for native service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsservicepythonloader.h" +#include "qgsserviceregistry.h" +#include "qgsmessagelog.h" +#include +#include + +typedef void unloadHook_t( QgsServiceModule* ); + +class QgsServicePythonModuleEntry +{ + public: + QgsServicePythonModuleEntry( const QString& location ) + : mLocation(location) + , mModule(nullptr) + {} + + QString mLocation; + QgsServiceModule* mModule; + unloadHook_t* mUnloadHook; +}; + + +//! Constructor +QgsServicePythonLoader::QgsServicePythonLoader() +{ +} + +//! Destructor +QgsServicePythonLoader::~QgsServicePythonLoader() +{ + +} + +void QgsServicePythonLoader::loadModules( const QString& modulePath, QgsServiceRegistry& registrar ) +{ + //TODO +} + + +void QgsServicePythonLoader::unloadModules() +{ + ModuleTable::iterator it = mModules.begin(); + ModuleTable::iterator end = mModules.end(); + + while( it!=end ) + { + unloadModuleEntry( it->get() ); + ++it; + } + + mModules.clear(); +} + + +void QgsServicePythonLoader::unloadModuleEntry( QgsServicePythonModuleEntry* entry ) +{ + // Call cleanup function if it exists + if( entry->mUnloadHook ) { + entry->mUnloadHook( entry->mModule ); + } +} + + diff --git a/src/server/qgsservicepythonloader.h b/src/server/qgsservicepythonloader.h new file mode 100644 index 00000000000..13b813c3d17 --- /dev/null +++ b/src/server/qgsservicepythonloader.h @@ -0,0 +1,73 @@ +/*************************************************************************** + qgsservicepythonloader.h + + Define Loader for python service modules + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVICEPYTHONLOADER_H +#define QGSSERVICEPYTHONLOADER_H + +class QgsServiceModule; +class QgsServicePythonModuleEntry; +class QgsServiceRegistry; + +#include "qgsserviceloader.h" +#include + +#include + +/** + * \ingroup server + * QgsServicePythonLoader + * Class defining the python service module loader for QGIS server services + * + * This class provides methods for loading and managing hook for native (C++) modules + * + */ +class SERVER_EXPORT QgsServicePythonLoader: public QgsServiceLoader +{ + public: + //! Constructor + QgsServicePythonLoader(); + + //! Destructor + ~QgsServicePythonLoader(); + + /** + * Lead all medules from path + * @param modulePath the path to look for module + * @parama registrar QgsServiceRegistry instance for registering services + */ + void loadModules( const QString& modulePath, QgsServiceRegistry& registrar ) override; + + /** + * Unload all modules + */ + void unloadModules() override; + + private: + typedef QHash > ModuleTable; + + /** + * Unload medule hook + */ + void unloadModuleEntry( QgsServicePythonModuleEntry* entry ); + + //! Associative storage for module handles + ModuleTable mModules; +}; + +#endif + diff --git a/src/server/qgsserviceregistry.cpp b/src/server/qgsserviceregistry.cpp new file mode 100644 index 00000000000..66d8b10c580 --- /dev/null +++ b/src/server/qgsserviceregistry.cpp @@ -0,0 +1,185 @@ +/*************************************************************************** + qgsserviceregistry.cpp + + Class defining the service manager for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsserviceregistry.h" +#include "qgsservice.h" +#include "qgslogger.h" +#include "qgsmessagelog.h" + +namespace { + + // Build a key entry from name and version + QString make_service_key( const QString& name, const QString& version ) + { + return QString( "%1_%2" ).arg(name,version); + } + + // Compare two version strings: + // The strings are splitted into dot separated segment + // Each segment are compared up to the shortest number of segment of the + // lists. Remaining segments are dropped. + // If both segments can be intepreted as numbers the are compared as numbers, otherwise + // They are compared lexicographically. + // Return true if v1 is greater than v2 + bool is_version_greater( const QString& v1, const QString& v2 ) + { + QStringList l1 = v1.split('.'); + QStringList l2 = v2.split('.'); + QStringList::iterator it1 = l1.begin(); + QStringList::iterator it2 = l2.begin(); + bool isint; + while( it1 != l1.end() && it2 != l2.end() ) + { + if ( *it1 != *it2 ) + { + // Compare as numbers + int i1 = it1->toInt(&isint); + if(isint) + { + int i2 = it2->toInt(&isint); + if( isint && i1 != i2 ) + { + return i1 > i2; + } + } + // Compare lexicographically + if ( !isint ) + { + return *it1 > *it2; + } + } + ++it1; + ++it2; + } + // We reach the end of one of the list + return false; + } + +} // namespace + +class QgsServiceEntry +{ + public: + ~QgsServiceEntry() + { + // We have the ownership by design + // XXX Take care of /Transfer/ decorator for registerService in sip + QgsDebugMsg( QString("Deleting service %1 %2").arg(mName, mVersion) ); + delete mService; + } + + QgsServiceEntry( const QString& name, QgsService* service, const QString& version ) + : mName(name) + , mService(service) + , mVersion(version) + {} + + QString mName; + QgsService* mService; + QString mVersion; +}; + +QgsServiceRegistry::QgsServiceRegistry() +{ + //TODO +} + +QgsServiceRegistry::~QgsServiceRegistry() +{ + cleanUp(); +} + +QgsService* QgsServiceRegistry::getService( const QString& name, const QString& version ) +{ + QgsService* service = nullptr; + QString key; + + // Check that we have a service of that name + VersionTable::iterator v = mVersions.find(name); + if( v != mVersions.end() ) + { + key = version.isEmpty() ? v->second : make_service_key(name, version ); + ServiceTable::iterator it = mServices.find(key); + if( it != mServices.end() ) + { + service = (*it)->mService; + } + else + { + QgsMessageLog::logMessage( QString("Service %1 %2 not found").arg(name, version) ); + } + } + else + { + QgsMessageLog::logMessage( QString("Service %1 is not registered").arg(name) ); + } + return service; +} + +void QgsServiceRegistry::registerService( const QString& name, QgsService* service, const QString& version ) +{ + // Test if service is already registered + QString key = make_service_key( name, version ); + if( mServices.find(key) != mServices.end() ) + { + QgsMessageLog::logMessage( QString("Error Service %1 %2 is already registered").arg(name,version) ); + return; + } + + QgsMessageLog::logMessage( QString( "Adding service %1 %2").arg(name,version) ); + mServices.insert( key, std::make_shared( name, service, version ) ); + + // Check the default version + // and replace with te new one if it has a higher version + VersionTable::iterator v = mVersions.find( name ); + if( v != mVersions.end() ) + { + if( is_version_greater( version, v->first ) ) + { + // Replace the default version key + mVersions.insert( name, VersionTable::mapped_type( version, key ) ); + } + } + else + { + // Insert the service as the default one + mVersions.insert( name, VersionTable::mapped_type( version, key ) ); + } +} + +void QgsServiceRegistry::init( const QString& nativeModulePath, const QString& pythonModulePath ) +{ + mNativeLoader.loadModules(nativeModulePath, *this ); + #ifdef HAVE_SERVER_PYTHON_SERVICES + mPythonLoader.loadModules(pythonModulePath, *this ); + #endif +} + +void QgsServiceRegistry::cleanUp() +{ + // Release all services + mServices.clear(); + + mNativeLoader.unloadModules(); + #ifdef HAVE_SERVER_PYTHON_SERVICES + mPythonLoader.unloadModules(); + #endif +} + + diff --git a/src/server/qgsserviceregistry.h b/src/server/qgsserviceregistry.h new file mode 100644 index 00000000000..34a08587263 --- /dev/null +++ b/src/server/qgsserviceregistry.h @@ -0,0 +1,105 @@ +/*************************************************************************** + qgsserviceregstry.h + + Class defining the service manager for QGIS server services. + ------------------- + begin : 2016-12-05 + copyright : (C) 2016 by David Marteau + email : david dot marteau at 3liz dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSSERVICEREGISTRY_H +#define QGSSERVICEREGISTRY_H + +#include "qgsconfig.h" + +#include +#include + +#include "qgsservicenativeloader.h" +#include "qgsservicepythonloader.h" +#include + +class QgsService; +class QgsServiceEntry; + +/** + * \ingroup server + * QgsServiceRegistry + * Class defining the cegistry manager for QGIS server services + * + * This class provides methods for registering and retrieving + * services. Note that the regstiry does not hord ownership of + * registered service but vill call the 'release' method on cleanup) + * + */ +class SERVER_EXPORT QgsServiceRegistry +{ + + public: + + //! Constructor + QgsServiceRegistry(); + + //! Destructor + ~QgsServiceRegistry(); + + /** + * Retrieve a service from its name + * @param name the name of the service + * @param version the version string (optional) + * @return QgsService + * + * If the version is not provided the higher version of the service is rerturnod + */ + QgsService* getService( const QString& name, const QString& version = QString() ); + + /** + * Register a service by its name + * + * This method is intended to be called by modules for registering + * services. A module may register multiple services. + * The registry gain ownership of services. + * + * @param name the name of the service + * @param service a QgsServerResponse to be registered + * @param version the version string for the service (required) + */ + void registerService( const QString& name, QgsService* service, const QString& version ); + + /** + * Initialize registry, load modules and auto register services + * @param nativeModulepath the native module path + * @param pythonModulePath the python module path + * + * If pythonModulePath is not specified the environnement variables QGIS_PYTHON_SERVICE_PATH + * is examined. + */ + void init( const QString& nativeModulepath, const QString& pythonModulePath = QString() ); + + /** + * Clean up registered service and unregister modules + */ + void cleanUp(); + + private: + typedef QHash > ServiceTable; + typedef QHash > VersionTable; + + QgsServiceNativeLoader mNativeLoader; + QgsServicePythonLoader mPythonLoader; + + ServiceTable mServices; + VersionTable mVersions; +}; + +#endif + diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index a5afff28276..158baff7653 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -172,4 +172,5 @@ IF (WITH_SERVER) ADD_PYTHON_TEST(PyQgsAuthManagerPasswordOWSTest test_authmanager_password_ows.py) ADD_PYTHON_TEST(PyQgsAuthManagerPKIOWSTest test_authmanager_pki_ows.py) ADD_PYTHON_TEST(PyQgsAuthManagerPKIPostgresTest test_authmanager_pki_postgres.py) + ADD_PYTHON_TEST(PyQgsServicesTest test_services.py) ENDIF (WITH_SERVER) diff --git a/tests/src/python/test_services.py b/tests/src/python/test_services.py new file mode 100644 index 00000000000..e63733f2be6 --- /dev/null +++ b/tests/src/python/test_services.py @@ -0,0 +1,89 @@ +""" QGIS test for server services +""" +from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream +from qgis.testing import unittest +from qgis.server import (QgsServiceRegistry, + QgsService, + QgsServerRequest, + QgsServerResponse) + + +class Response(QgsServerResponse): + + def __init__( self ): + QgsServerResponse.__init__(self) + self._buffer = QBuffer() + self._buffer.open(QIODevice.ReadWrite) + + + def setReturnCode( self, code ): + pass + + def setHeader( self, key, val ): + pass + + def sendError( self, code, message ): + pass + + def io(self): + return self._buffer + + +class MyService(QgsService): + + def __init__(self, response): + QgsService.__init__(self) + self._response = response + + def executeRequest( self, request, response ): + + url = request.url() + + response.setReturnCode(201) + response.write(self._response) + + +class TestServices(unittest.TestCase): + """ + """ + + def test_register(self): + + reg = QgsServiceRegistry() + + myserv = MyService("Hello world") + + reg.registerService("STUFF", myserv, "1.0" ) + + # Retrieve service + request = QgsServerRequest("http://DoStufff", QgsServerRequest.GetMethod) + response = Response() + + service = reg.getService("STUFF") + if service: + service.executeRequest(request, response) + + io = response.io(); + io.seek(0) + + self.assertEquals(QTextStream(io).readLine(), "Hello world") + + def test_version_registration(self): + + reg = QgsServiceRegistry() + myserv1 = MyService("1.0") + myserv2 = MyService("1.1") + + reg.registerService("STUFF", myserv1, myserv1._response) + reg.registerService("STUFF", myserv2, myserv2._response) + + + service = reg.getService("STUFF") + self.assertIsNotNone(service) + self.assertEquals(service._response, myserv2._response) + + service = reg.getService("STUFF", "2.0") + self.assertIsNone(service) + +if __name__ == '__main__': + unittest.main()