diff --git a/python/server/qgsserversettings.sip b/python/server/qgsserversettings.sip new file mode 100644 index 00000000000..3fe1202538d --- /dev/null +++ b/python/server/qgsserversettings.sip @@ -0,0 +1,89 @@ +/*************************************************************************** + qgsserversettings.sip + --------------------- + begin : December 19, 2016 + copyright : (C) 2016 by Paul Blottiere + email : paul dot blottiere at oslandia 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 + * QgsServerSettings provides a way to retrieve settings by prioritizing + * according to environment variables, ini file and default values. + * @note added in QGIS 3.0 + */ +class QgsServerSettings +{ +%TypeHeaderCode +#include "qgsserversettings.h" +%End + + public: + /** Constructor. + */ + QgsServerSettings(); + + /** Load settings according to current environment variables. + */ + void load(); + + /** Log a summary of settings curently loaded. + */ + void logSummary() const; + + /** Returns the ini file loaded by QSetting. + * @return the path of the ini file or an empty string if none is loaded. + */ + QString iniFile() const; + + /** Returns the maximum number of threads to use. + * @return the number of threads. + */ + int maxThreads() const; + + /** Returns parallel rendering setting. + * @return true if parallel rendering is activated, false otherwise. + */ + bool parallelRendering() const; + + /** Returns the log level. + * @return the log level. + */ + QgsMessageLog::MessageLevel logLevel() const; + + /** Returns the log file. + * @return the path of the log file or an empty string if none is defined. + */ + QString logFile() const; + + /** Returns the QGS project file to use. + * @return the path of the QGS project or an empty string if none is defined. + */ + QString projectFile() const; + + /** + * Returns the maximum number of cached layers. + * @return the number of cached layers. + */ + int maxCacheLayers() const; + + /** Returns the cache size. + * @return the cache size. + */ + qint64 cacheSize() const; + + /** Returns the cache directory. + * @return the directory. + */ + QString cacheDirectory() const; +}; diff --git a/python/server/qgswcserver.sip b/python/server/qgswcserver.sip index 441712aa5e3..cd44f5062dc 100644 --- a/python/server/qgswcserver.sip +++ b/python/server/qgswcserver.sip @@ -36,7 +36,7 @@ class QgsWCSServer: public QgsOWSServer { public: /** Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/ - QgsWCSServer( const QString& configFilePath, QMap& parameters, QgsWCSProjectParser* pp, + QgsWCSServer( const QString& configFilePath, const QgsServerSettings& settings, QMap& parameters, QgsWCSProjectParser* pp, QgsRequestHandler* rh, const QgsAccessControl* accessControl ); ~QgsWCSServer(); diff --git a/python/server/qgswfserver.sip b/python/server/qgswfserver.sip index df1d5b92754..407fff05bb0 100644 --- a/python/server/qgswfserver.sip +++ b/python/server/qgswfserver.sip @@ -59,7 +59,8 @@ class QgsWfsServer: public QgsOWSServer { public: /** Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/ - QgsWfsServer( const QString& configFilePath, QMap& parameters, QgsWfsProjectParser* cp, + QgsWfsServer( const QString& configFilePath, const QgsServerSettings& settings, + QMap& parameters, QgsWfsProjectParser* cp, QgsRequestHandler* rh, const QgsAccessControl* accessControl ); ~QgsWfsServer(); diff --git a/python/server/qgswmserver.sip b/python/server/qgswmserver.sip index f295c0890c7..95b20433bfb 100644 --- a/python/server/qgswmserver.sip +++ b/python/server/qgswmserver.sip @@ -60,7 +60,7 @@ class QgsWmsServer: public QgsOWSServer public: /** Constructor. Does _NOT_ take ownership of QgsConfigParser, QgsCapabilitiesCache*/ - QgsWmsServer( const QString& configFilePath, QMap ¶meters, QgsWmsConfigParser* cp, QgsRequestHandler* rh, QgsCapabilitiesCache* capCache, const QgsAccessControl* accessControl ); + QgsWmsServer( const QString& configFilePath, const QgsServerSettings& settings, QMap ¶meters, QgsWmsConfigParser* cp, QgsRequestHandler* rh, QgsCapabilitiesCache* capCache, const QgsAccessControl* accessControl ); ~QgsWmsServer(); void executeRequest() override; diff --git a/python/server/server.sip b/python/server/server.sip index 625de7d9a1f..e75d5dcba03 100644 --- a/python/server/server.sip +++ b/python/server/server.sip @@ -27,6 +27,7 @@ %Include qgswmsprojectparser.sip %Include qgswfsprojectparser.sip %Include qgsconfigcache.sip +%Include qgsserversettings.sip %Include qgsserver.sip %Include qgsserverrequest.sip diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index cff2f86078f..eae5662abd4 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -27,6 +27,7 @@ SET ( qgis_mapserv_SRCS qgsowsserver.cpp qgswfsserver.cpp qgswcsserver.cpp + qgsserversettings.cpp qgsmapserviceexception.cpp qgsmslayercache.cpp qgsmslayerbuilder.cpp @@ -75,6 +76,7 @@ SET (qgis_mapserv_MOC_HDRS # qgshttptransaction.h qgsmslayercache.h qgsserverlogger.h + qgsserversettings.h qgsserverstreamingdevice.h ) diff --git a/src/server/qgsaccesscontrol.cpp b/src/server/qgsaccesscontrol.cpp index f8fe1480ed1..73db06e2ec5 100644 --- a/src/server/qgsaccesscontrol.cpp +++ b/src/server/qgsaccesscontrol.cpp @@ -22,12 +22,25 @@ #include +void QgsAccessControl::resolveFilterFeatures( const QList &layers ) +{ + Q_FOREACH ( QgsMapLayer* l, layers ) + { + if ( l->type() == QgsMapLayer::LayerType::VectorLayer ) + { + const QgsVectorLayer* vl = qobject_cast( l ); + mFilterFeaturesExpressions[vl->id()] = resolveFilterFeatures( vl ); + } + } -//! Filter the features of the layer -void QgsAccessControl::filterFeatures( const QgsVectorLayer* layer, QgsFeatureRequest& featureRequest ) const + mResolved = true; +} + +QString QgsAccessControl::resolveFilterFeatures( const QgsVectorLayer* layer ) const { QStringList expressions = QStringList(); QgsAccessControlFilterMap::const_iterator acIterator; + for ( acIterator = mPluginsAccessControls->constBegin(); acIterator != mPluginsAccessControls->constEnd(); ++acIterator ) { QString expression = acIterator.value()->layerFilterExpression( layer ); @@ -36,9 +49,34 @@ void QgsAccessControl::filterFeatures( const QgsVectorLayer* layer, QgsFeatureRe expressions.append( expression ); } } + + QString expression; if ( !expressions.isEmpty() ) { - featureRequest.setFilterExpression( QStringLiteral( "((" ).append( expressions.join( QStringLiteral( ") AND (" ) ) ).append( "))" ) ); + expression = QStringLiteral( "((" ).append( expressions.join( QStringLiteral( ") AND (" ) ) ).append( "))" ); + } + + return expression; +} + +//! Filter the features of the layer +void QgsAccessControl::filterFeatures( const QgsVectorLayer* layer, QgsFeatureRequest& featureRequest ) const +{ + + QString expression; + + if ( mResolved && mFilterFeaturesExpressions.keys().contains( layer->id() ) ) + { + expression = mFilterFeaturesExpressions[layer->id()]; + } + else + { + expression = resolveFilterFeatures( layer ); + } + + if ( !expression.isEmpty() ) + { + featureRequest.setFilterExpression( expression ); } } diff --git a/src/server/qgsaccesscontrol.h b/src/server/qgsaccesscontrol.h index 539b515e42f..7579b0e648f 100644 --- a/src/server/qgsaccesscontrol.h +++ b/src/server/qgsaccesscontrol.h @@ -40,12 +40,15 @@ class SERVER_EXPORT QgsAccessControl : public QgsFeatureFilterProvider QgsAccessControl() { mPluginsAccessControls = new QgsAccessControlFilterMap(); + mResolved = false; } //! Constructor QgsAccessControl( const QgsAccessControl& copy ) { mPluginsAccessControls = new QgsAccessControlFilterMap( *copy.mPluginsAccessControls ); + mFilterFeaturesExpressions = copy.mFilterFeaturesExpressions; + mResolved = copy.mResolved; } @@ -54,6 +57,11 @@ class SERVER_EXPORT QgsAccessControl : public QgsFeatureFilterProvider delete mPluginsAccessControls; } + /** Resolve features' filter of layers + * @param layers to filter + */ + void resolveFilterFeatures( const QList &layers ); + /** Filter the features of the layer * @param layer the layer to control * @param filterFeatures the request to fill @@ -122,8 +130,13 @@ class SERVER_EXPORT QgsAccessControl : public QgsFeatureFilterProvider void registerAccessControl( QgsAccessControlFilter* accessControl, int priority = 0 ); private: + QString resolveFilterFeatures( const QgsVectorLayer* layer ) const; + //! The AccessControl plugins registry QgsAccessControlFilterMap* mPluginsAccessControls; + + QMap mFilterFeaturesExpressions; + bool mResolved; }; #endif diff --git a/src/server/qgsmslayercache.cpp b/src/server/qgsmslayercache.cpp index 09f6f1dc5db..4e40c6a07c9 100644 --- a/src/server/qgsmslayercache.cpp +++ b/src/server/qgsmslayercache.cpp @@ -21,6 +21,7 @@ #include "qgsmaplayer.h" #include "qgsvectorlayer.h" #include "qgslogger.h" +#include "qgsserversettings.h" #include QgsMSLayerCache* QgsMSLayerCache::instance() @@ -32,20 +33,8 @@ QgsMSLayerCache* QgsMSLayerCache::instance() } QgsMSLayerCache::QgsMSLayerCache() - : mProjectMaxLayers( 0 ) + : mProjectMaxLayers( 100 ) { - mDefaultMaxLayers = 100; - //max layer from environment variable overrides default - char* maxLayerEnv = getenv( "MAX_CACHE_LAYERS" ); - if ( maxLayerEnv ) - { - bool conversionOk = false; - int maxLayerInt = QString( maxLayerEnv ).toInt( &conversionOk ); - if ( conversionOk ) - { - mDefaultMaxLayers = maxLayerInt; - } - } QObject::connect( &mFileSystemWatcher, SIGNAL( fileChanged( const QString& ) ), this, SLOT( removeProjectFileLayers( const QString& ) ) ); } @@ -59,6 +48,11 @@ QgsMSLayerCache::~QgsMSLayerCache() mEntries.clear(); } +void QgsMSLayerCache::setMaxCacheLayers( int maxCacheLayers ) +{ + mDefaultMaxLayers = maxCacheLayers; +} + void QgsMSLayerCache::insertLayer( const QString& url, const QString& layerName, QgsMapLayer* layer, const QString& configFile, const QList& tempFiles ) { QgsMessageLog::logMessage( "Layer cache: insert Layer '" + layerName + "' configFile: " + configFile, QStringLiteral( "Server" ), QgsMessageLog::INFO ); diff --git a/src/server/qgsmslayercache.h b/src/server/qgsmslayercache.h index 2ed70f769f0..ef00d187b65 100644 --- a/src/server/qgsmslayercache.h +++ b/src/server/qgsmslayercache.h @@ -56,6 +56,13 @@ class QgsMSLayerCache: public QObject static QgsMSLayerCache* instance(); ~QgsMSLayerCache(); + /** + * Set the maximum number of layers in cache. + * @param maxCacheLayers the number of layers in cache + * @note added in QGIS 3.0 + */ + void setMaxCacheLayers( int maxCacheLayers ); + /** Inserts a new layer into the cash @param url the layer datasource @param layerName the layer name (to distinguish between different layers in a request using the same datasource diff --git a/src/server/qgsowsserver.h b/src/server/qgsowsserver.h index b2c4b5efb4b..36de1f425b4 100644 --- a/src/server/qgsowsserver.h +++ b/src/server/qgsowsserver.h @@ -20,6 +20,7 @@ #include "qgsconfig.h" #include "qgsrequesthandler.h" +#include "qgsserversettings.h" #ifdef HAVE_SERVER_PYTHON_PLUGINS #include "qgsaccesscontrol.h" #else @@ -35,11 +36,13 @@ class QgsOWSServer public: QgsOWSServer( const QString& configFilePath + , const QgsServerSettings& settings , const QMap& parameters , QgsRequestHandler* rh - , const QgsAccessControl* ac + , QgsAccessControl* ac ) - : mParameters( parameters ) + : mSettings( settings ) + , mParameters( parameters ) , mRequestHandler( rh ) , mConfigFilePath( configFilePath ) , mAccessControl( ac ) @@ -57,12 +60,13 @@ class QgsOWSServer QgsOWSServer() {} protected: + QgsServerSettings mSettings; QMap mParameters; QgsRequestHandler* mRequestHandler; QString mConfigFilePath; //! The access control helper - const QgsAccessControl* mAccessControl; + QgsAccessControl* mAccessControl; #ifdef HAVE_SERVER_PYTHON_PLUGINS diff --git a/src/server/qgsserver.cpp b/src/server/qgsserver.cpp index 8050d800b49..a32ac62f5c3 100644 --- a/src/server/qgsserver.cpp +++ b/src/server/qgsserver.cpp @@ -21,6 +21,7 @@ //for CMAKE_INSTALL_PREFIX #include "qgsconfig.h" #include "qgsserver.h" +#include "qgsmslayercache.h" #include "qgsmapsettings.h" #include "qgsauthmanager.h" @@ -64,6 +65,7 @@ QgsCapabilitiesCache* QgsServer::sCapabilitiesCache = nullptr; QgsServerInterfaceImpl* QgsServer::sServerInterface = nullptr; // Initialization must run once for all servers bool QgsServer::sInitialised = false; +QgsServerSettings QgsServer::sSettings; QgsServiceRegistry QgsServer::sServiceRegistry; @@ -99,12 +101,8 @@ void QgsServer::setupNetworkAccessManager() QSettings settings; QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance(); QNetworkDiskCache *cache = new QNetworkDiskCache( nullptr ); - QString cacheDirectory = settings.value( QStringLiteral( "cache/directory" ) ).toString(); - if ( cacheDirectory.isEmpty() ) - cacheDirectory = QgsApplication::qgisSettingsDirPath() + "cache"; - qint64 cacheSize = settings.value( QStringLiteral( "cache/size" ), 50 * 1024 * 1024 ).toULongLong(); - QgsMessageLog::logMessage( QStringLiteral( "setCacheDirectory: %1" ).arg( cacheDirectory ), QStringLiteral( "Server" ), QgsMessageLog::INFO ); - QgsMessageLog::logMessage( QStringLiteral( "setMaximumCacheSize: %1" ).arg( cacheSize ), QStringLiteral( "Server" ), QgsMessageLog::INFO ); + qint64 cacheSize = sSettings.cacheSize(); + QString cacheDirectory = sSettings.cacheDirectory(); cache->setCacheDirectory( cacheDirectory ); cache->setMaximumCacheSize( cacheSize ); QgsMessageLog::logMessage( QStringLiteral( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), QStringLiteral( "Server" ), QgsMessageLog::INFO ); @@ -214,7 +212,7 @@ void QgsServer::printRequestInfos() QString QgsServer::configPath( const QString& defaultConfigPath, const QMap& parameters ) { QString cfPath( defaultConfigPath ); - QString projectFile = getenv( "QGIS_PROJECT_FILE" ); + QString projectFile = sSettings.projectFile(); if ( !projectFile.isEmpty() ) { cfPath = projectFile; @@ -247,16 +245,6 @@ bool QgsServer::init( ) return false; } - QgsServerLogger::instance(); - - QString optionsPath = getenv( "QGIS_OPTIONS_PATH" ); - if ( !optionsPath.isEmpty() ) - { - QgsMessageLog::logMessage( "Options PATH: " + optionsPath, QStringLiteral( "Server" ), QgsMessageLog::INFO ); - QSettings::setDefaultFormat( QSettings::IniFormat ); - QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, optionsPath ); - } - QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME ); QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN ); QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME ); @@ -277,6 +265,22 @@ bool QgsServer::init( ) QgsApplication::skipGdalDriver( "JP2ECW" ); #endif + // reload settings to take into account QCoreApplication and QgsApplication + // configuration + sSettings.load(); + + // init and configure logger + QgsServerLogger::instance(); + QgsServerLogger::instance()->setLogLevel( sSettings.logLevel() ); + QgsServerLogger::instance()->setLogFile( sSettings.logFile() ); + + // init and configure cache + QgsMSLayerCache::instance(); + QgsMSLayerCache::instance()->setMaxCacheLayers( sSettings.maxCacheLayers() ); + + // log settings currently used + sSettings.logSummary(); + setupNetworkAccessManager(); QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars ); @@ -324,7 +328,7 @@ bool QgsServer::init( ) QgsEditorWidgetRegistry::initEditors(); - sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, &sServiceRegistry ); + sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, &sServiceRegistry, &sSettings ); // Load service module QString modulePath = QgsApplication::libexecPath() + "server"; @@ -345,6 +349,7 @@ void QgsServer::putenv( const QString &var, const QString &val ) #else setenv( var.toStdString().c_str(), val.toStdString().c_str(), 1 ); #endif + sSettings.load( var ); } /** @@ -394,7 +399,7 @@ void QgsServer::handleRequest( QgsServerRequest& request, QgsServerResponse& res QMap parameterMap = request.parameters(); printRequestParameters( parameterMap, logLevel ); - const QgsAccessControl* accessControl = sServerInterface->accessControls(); + QgsAccessControl* accessControl = sServerInterface->accessControls(); //Config file path QString configFilePath = configPath( *sConfigFilePath, parameterMap ); @@ -447,6 +452,7 @@ void QgsServer::handleRequest( QgsServerRequest& request, QgsServerResponse& res { QgsWCSServer wcsServer( configFilePath + , sSettings , parameterMap , p , &theRequestHandler @@ -469,6 +475,7 @@ void QgsServer::handleRequest( QgsServerRequest& request, QgsServerResponse& res { QgsWfsServer wfsServer( configFilePath + , sSettings , parameterMap , p , &theRequestHandler diff --git a/src/server/qgsserver.h b/src/server/qgsserver.h index a13decc5b50..7d3ce44dcb3 100644 --- a/src/server/qgsserver.h +++ b/src/server/qgsserver.h @@ -35,6 +35,7 @@ #include "qgsmapsettings.h" #include "qgsmessagelog.h" #include "qgsserviceregistry.h" +#include "qgsserversettings.h" #include "qgsserverplugins.h" #include "qgsserverfilter.h" #include "qgsserverinterfaceimpl.h" @@ -132,6 +133,7 @@ class SERVER_EXPORT QgsServer //! service registry static QgsServiceRegistry sServiceRegistry; + static QgsServerSettings sSettings; }; #endif // QGSSERVER_H diff --git a/src/server/qgsserverinterface.h b/src/server/qgsserverinterface.h index 907c4ac5643..d5177b91120 100644 --- a/src/server/qgsserverinterface.h +++ b/src/server/qgsserverinterface.h @@ -25,6 +25,7 @@ #include "qgscapabilitiescache.h" #include "qgsrequesthandler.h" #include "qgsserverfilter.h" +#include "qgsserversettings.h" #ifdef HAVE_SERVER_PYTHON_PLUGINS #include "qgsaccesscontrolfilter.h" #include "qgsaccesscontrol.h" @@ -111,7 +112,7 @@ class SERVER_EXPORT QgsServerInterface virtual void registerAccessControl( QgsAccessControlFilter* accessControl, int priority = 0 ) = 0; //! Gets the registred access control filters - virtual const QgsAccessControl* accessControls() const = 0; + virtual QgsAccessControl* accessControls() const = 0; //! Return an enrironment variable, used to pass environment variables to python virtual QString getEnv( const QString& name ) const = 0; @@ -146,6 +147,14 @@ class SERVER_EXPORT QgsServerInterface */ virtual QgsServiceRegistry* serviceRegistry() = 0; + /** + * Return the server settings + * @return QgsServerSettings + * + * @note not available in python bindings + */ + virtual QgsServerSettings* serverSettings() = 0; + private: QString mConfigFilePath; }; diff --git a/src/server/qgsserverinterfaceimpl.cpp b/src/server/qgsserverinterfaceimpl.cpp index 94708590ba9..3b8855aa41c 100644 --- a/src/server/qgsserverinterfaceimpl.cpp +++ b/src/server/qgsserverinterfaceimpl.cpp @@ -22,9 +22,10 @@ #include "qgsmslayercache.h" //! Constructor -QgsServerInterfaceImpl::QgsServerInterfaceImpl( QgsCapabilitiesCache* capCache, QgsServiceRegistry* srvRegistry ) +QgsServerInterfaceImpl::QgsServerInterfaceImpl( QgsCapabilitiesCache* capCache, QgsServiceRegistry* srvRegistry, QgsServerSettings* settings ) : mCapabilitiesCache( capCache ) , mServiceRegistry( srvRegistry ) + , mServerSettings( settings ) { mRequestHandler = nullptr; #ifdef HAVE_SERVER_PYTHON_PLUGINS @@ -100,3 +101,7 @@ QgsServiceRegistry* QgsServerInterfaceImpl::serviceRegistry() return mServiceRegistry; } +QgsServerSettings* QgsServerInterfaceImpl::serverSettings() +{ + return mServerSettings; +} diff --git a/src/server/qgsserverinterfaceimpl.h b/src/server/qgsserverinterfaceimpl.h index efce6ca93b3..c5680456fb3 100644 --- a/src/server/qgsserverinterfaceimpl.h +++ b/src/server/qgsserverinterfaceimpl.h @@ -36,7 +36,8 @@ class QgsServerInterfaceImpl : public QgsServerInterface //! Constructor explicit QgsServerInterfaceImpl( QgsCapabilitiesCache *capCache, - QgsServiceRegistry* srvRegistry ); + QgsServiceRegistry* srvRegistry, + QgsServerSettings* serverSettings ); ~QgsServerInterfaceImpl(); @@ -55,7 +56,7 @@ class QgsServerInterfaceImpl : public QgsServerInterface /** Gets the helper over all the registered access control filters * @return the access control helper */ - const QgsAccessControl* accessControls() const override { return mAccessControls; } + QgsAccessControl* accessControls() const override { return mAccessControls; } QString getEnv( const QString& name ) const override; QString configFilePath() override { return mConfigFilePath; } void setConfigFilePath( const QString& configFilePath ) override; @@ -65,6 +66,8 @@ class QgsServerInterfaceImpl : public QgsServerInterface QgsServiceRegistry* serviceRegistry() override; + QgsServerSettings* serverSettings() override; + private: QString mConfigFilePath; @@ -73,6 +76,7 @@ class QgsServerInterfaceImpl : public QgsServerInterface QgsCapabilitiesCache* mCapabilitiesCache; QgsRequestHandler* mRequestHandler; QgsServiceRegistry* mServiceRegistry; + QgsServerSettings* mServerSettings; }; #endif // QGSSERVERINTERFACEIMPL_H diff --git a/src/server/qgsserverlogger.cpp b/src/server/qgsserverlogger.cpp index c145feaa26a..ea2828c82a8 100644 --- a/src/server/qgsserverlogger.cpp +++ b/src/server/qgsserverlogger.cpp @@ -39,28 +39,33 @@ QgsServerLogger::QgsServerLogger() : mLogFile( nullptr ) , mLogLevel( QgsMessageLog::NONE ) { - //logfile - QString filePath = getenv( "QGIS_SERVER_LOG_FILE" ); - if ( filePath.isEmpty() ) - return; - - mLogFile.setFileName( filePath ); - if ( mLogFile.open( QIODevice::Append ) ) - { - mTextStream.setDevice( &mLogFile ); - } - - //log level - char* logLevelChar = getenv( "QGIS_SERVER_LOG_LEVEL" ); - if ( logLevelChar ) - { - mLogLevel = static_cast( atoi( logLevelChar ) ); - } - connect( QgsApplication::messageLog(), SIGNAL( messageReceived( QString, QString, QgsMessageLog::MessageLevel ) ), this, SLOT( logMessage( QString, QString, QgsMessageLog::MessageLevel ) ) ); } +void QgsServerLogger::setLogLevel( QgsMessageLog::MessageLevel level ) +{ + mLogLevel = level; +} + +void QgsServerLogger::setLogFile( const QString& f ) +{ + if ( ! f.isEmpty() ) + { + if ( mLogFile.exists() ) + { + mTextStream.flush(); + mLogFile.close(); + } + + mLogFile.setFileName( f ); + if ( mLogFile.open( QIODevice::Append ) ) + { + mTextStream.setDevice( &mLogFile ); + } + } +} + void QgsServerLogger::logMessage( const QString& message, const QString& tag, QgsMessageLog::MessageLevel level ) { Q_UNUSED( tag ); diff --git a/src/server/qgsserverlogger.h b/src/server/qgsserverlogger.h index 2174719a304..025ee1ba99f 100644 --- a/src/server/qgsserverlogger.h +++ b/src/server/qgsserverlogger.h @@ -38,9 +38,23 @@ class QgsServerLogger: public QObject /** * Get the current log level + * @return the log level + * @note added in QGIS 3.0 */ QgsMessageLog::MessageLevel logLevel() const { return mLogLevel; } + /** + * Set the current log level + * @param level the log level + * @note added in QGIS 3.0 + */ + void setLogLevel( QgsMessageLog::MessageLevel level ); + + /** + * Set the current log file + */ + void setLogFile( const QString& f ); + public slots: /** diff --git a/src/server/qgsserversettings.cpp b/src/server/qgsserversettings.cpp new file mode 100644 index 00000000000..85eeb26da6c --- /dev/null +++ b/src/server/qgsserversettings.cpp @@ -0,0 +1,307 @@ +/*************************************************************************** + qgsserversettings.cpp + --------------------- + begin : December 19, 2016 + copyright : (C) 2016 by Paul Blottiere + email : paul dot blottiere at oslandia 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 "qgsserversettings.h" +#include "qgsapplication.h" + +#include + +#include + +QgsServerSettings::QgsServerSettings() +{ + load(); +} + +void QgsServerSettings::initSettings() +{ + mSettings.clear(); + + // options path + const Setting sOptPath = { QgsServerSettingsEnv::QGIS_OPTIONS_PATH, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Override the default path for user configuration", + "", + QVariant::String, + QVariant( "" ), + QVariant() + }; + mSettings[ sOptPath.envVar ] = sOptPath; + + // parallel rendering + const Setting sParRend = { QgsServerSettingsEnv::QGIS_SERVER_PARALLEL_RENDERING, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Activate/Deactivate parallel rendering for WMS getMap request", + "/qgis/parallel_rendering", + QVariant::Bool, + QVariant( false ), + QVariant() + }; + mSettings[ sParRend.envVar ] = sParRend; + + // max threads + const Setting sMaxThreads = { QgsServerSettingsEnv::QGIS_SERVER_MAX_THREADS, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Number of threads to use when parallel rendering is activated", + "/qgis/max_threads", + QVariant::Int, + QVariant( -1 ), + QVariant() + }; + mSettings[ sMaxThreads.envVar ] = sMaxThreads; + + // log level + const Setting sLogLevel = { QgsServerSettingsEnv::QGIS_SERVER_LOG_LEVEL, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Log level", + "", + QVariant::Int, + QVariant( QgsMessageLog::NONE ), + QVariant() + }; + mSettings[ sLogLevel.envVar ] = sLogLevel; + + // log file + const Setting sLogFile = { QgsServerSettingsEnv::QGIS_SERVER_LOG_FILE, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Log file", + "", + QVariant::String, + QVariant( "" ), + QVariant() + }; + mSettings[ sLogFile.envVar ] = sLogFile; + + // project file + const Setting sProject = { QgsServerSettingsEnv::QGIS_PROJECT_FILE, + QgsServerSettingsEnv::DEFAULT_VALUE, + "QGIS project file", + "", + QVariant::String, + QVariant( "" ), + QVariant() + }; + mSettings[ sProject.envVar ] = sProject; + + // max cache layers + const Setting sMaxCacheLayers = { QgsServerSettingsEnv::MAX_CACHE_LAYERS, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Specify the maximum number of cached layers", + "", + QVariant::Int, + QVariant( 100 ), + QVariant() + }; + mSettings[ sMaxCacheLayers.envVar ] = sMaxCacheLayers; + + // cache directory + const Setting sCacheDir = { QgsServerSettingsEnv::QGIS_SERVER_CACHE_DIRECTORY, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Specify the cache directory", + "/cache/directory", + QVariant::String, + QVariant( QgsApplication::qgisSettingsDirPath() + "cache" ), + QVariant() + }; + mSettings[ sCacheDir.envVar ] = sCacheDir; + + // cache size + const Setting sCacheSize = { QgsServerSettingsEnv::QGIS_SERVER_CACHE_SIZE, + QgsServerSettingsEnv::DEFAULT_VALUE, + "Specify the cache size", + "/cache/size", + QVariant::LongLong, + QVariant( 50*1024*1024 ), + QVariant() + }; + mSettings[ sCacheSize.envVar ] = sCacheSize; +} + +void QgsServerSettings::load() +{ + // init settings each time to take into account QgsApplication and + // QCoreApplication configuration for some default values + initSettings(); + + // store environment variables + QMap env = getEnv(); + + // load QSettings if QGIS_OPTIONS_PATH is defined + loadQSettings( env[ QgsServerSettingsEnv::QGIS_OPTIONS_PATH ] ); + + // prioritize values: 'env var' -> 'ini file' -> 'default value' + prioritize( env ); +} + +bool QgsServerSettings::load( const QString& envVarName ) +{ + bool rc( false ); + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + const int value = metaEnum.keyToValue( envVarName.toStdString().c_str() ); + + if ( value >= 0 ) + { + const QString envValue( getenv( envVarName.toStdString().c_str() ) ); + prioritize( QMap { {( QgsServerSettingsEnv::EnvVar ) value, envValue } } ); + rc = true; + } + + return rc; +} + +QMap QgsServerSettings::getEnv() const +{ + QMap env; + + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + for ( int i = 0; i < metaEnum.keyCount(); i++ ) + { + env[( QgsServerSettingsEnv::EnvVar ) metaEnum.value( i )] = getenv( metaEnum.key( i ) ); + } + + return env; +} + +QVariant QgsServerSettings::value( QgsServerSettingsEnv::EnvVar envVar ) const +{ + if ( mSettings[ envVar ].src == QgsServerSettingsEnv::DEFAULT_VALUE ) + { + return mSettings[ envVar ].defaultVal; + } + else + { + return mSettings[ envVar ].val; + } +} + +void QgsServerSettings::loadQSettings( const QString& envOptPath ) const +{ + if ( ! envOptPath.isEmpty() ) + { + QSettings::setDefaultFormat( QSettings::IniFormat ); + QSettings::setPath( QSettings::IniFormat, QSettings::UserScope, envOptPath ); + } +} + +void QgsServerSettings::prioritize( const QMap& env ) +{ +for ( QgsServerSettingsEnv::EnvVar e : env.keys() ) + { + Setting s = mSettings[ e ]; + + QVariant varValue; + if ( ! env.value( e ).isEmpty() ) + { + varValue.setValue( env.value( e ) ); + } + + if ( ! varValue.isNull() && varValue.canConvert( s.type ) ) + { + s.val = varValue; + s.src = QgsServerSettingsEnv::ENVIRONMENT_VARIABLE; + } + else if ( ! s.iniKey.isEmpty() && QSettings().contains( s.iniKey ) && QSettings().value( s.iniKey ).canConvert( s.type ) ) + { + s.val = QSettings().value( s.iniKey ); + s.src = QgsServerSettingsEnv::INI_FILE; + } + else + { + s.val = QVariant(); + s.src = QgsServerSettingsEnv::DEFAULT_VALUE; + } + + // an empty string can be returned from QSettings. In this case, we want + // to use the default value + if ( s.type == QVariant::String && s.val.toString().isEmpty() ) + { + s.val = QVariant(); + s.src = QgsServerSettingsEnv::DEFAULT_VALUE; + } + + mSettings[ e ] = s; + } +} + +void QgsServerSettings::logSummary() const +{ + const QMetaEnum metaEnumSrc( QMetaEnum::fromType() ); + const QMetaEnum metaEnumEnv( QMetaEnum::fromType() ); + + QgsMessageLog::logMessage( "Qgis Server Settings: ", "Server", QgsMessageLog::INFO ); +for ( Setting s : mSettings ) + { + const QString src = metaEnumSrc.valueToKey( s.src ); + const QString var = metaEnumEnv.valueToKey( s.envVar ); + + const QString msg = " - " + var + " / '" + s.iniKey + "' (" + s.descr + "): '" + value( s.envVar ).toString() + "' (read from " + src + ")"; + QgsMessageLog::logMessage( msg, "Server", QgsMessageLog::INFO ); + } + + if ( ! iniFile().isEmpty() ) + { + const QString msg = "Ini file used to initialize settings: " + iniFile(); + QgsMessageLog::logMessage( msg, "Server", QgsMessageLog::INFO ); + } +} + +// getter +QString QgsServerSettings::iniFile() const +{ + return QSettings().fileName(); +} + +bool QgsServerSettings::parallelRendering() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_PARALLEL_RENDERING ).toBool(); +} + +int QgsServerSettings::maxThreads() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_MAX_THREADS ).toInt(); +} + +QString QgsServerSettings::logFile() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_LOG_FILE ).toString(); +} + +QgsMessageLog::MessageLevel QgsServerSettings::logLevel() const +{ + return static_cast( value( QgsServerSettingsEnv::QGIS_SERVER_LOG_LEVEL ).toInt() ); +} + +int QgsServerSettings::maxCacheLayers() const +{ + return value( QgsServerSettingsEnv::MAX_CACHE_LAYERS ).toInt(); +} + +QString QgsServerSettings::projectFile() const +{ + return value( QgsServerSettingsEnv::QGIS_PROJECT_FILE ).toString(); +} + +qint64 QgsServerSettings::cacheSize() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_CACHE_SIZE ).toLongLong(); +} + +QString QgsServerSettings::cacheDirectory() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_CACHE_DIRECTORY ).toString(); +} diff --git a/src/server/qgsserversettings.h b/src/server/qgsserversettings.h new file mode 100644 index 00000000000..01b0f29a0ef --- /dev/null +++ b/src/server/qgsserversettings.h @@ -0,0 +1,154 @@ +/*************************************************************************** + qgsserversettings.h + ------------------- + begin : December 19, 2016 + copyright : (C) 2016 by Paul Blottiere + email : paul dot blottiere at oslandia 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 QGSSERVERSETTINGS_H +#define QGSSERVERSETTINGS_H + +#include +#include + +#include "qgsmessagelog.h" +#include "qgis_server.h" + +/** + * QgsServerSettingsEnv provides some enum describing the environment + * currently supported for configuration. + * @note added in QGIS 3.0 + */ +class QgsServerSettingsEnv : public QObject +{ + Q_OBJECT + + public: + enum Source + { + DEFAULT_VALUE, + ENVIRONMENT_VARIABLE, + INI_FILE + }; + Q_ENUM( Source ) + + enum EnvVar + { + QGIS_OPTIONS_PATH, + QGIS_SERVER_PARALLEL_RENDERING, + QGIS_SERVER_MAX_THREADS, + QGIS_SERVER_LOG_LEVEL, + QGIS_SERVER_LOG_FILE, + QGIS_PROJECT_FILE, + MAX_CACHE_LAYERS, + QGIS_SERVER_CACHE_DIRECTORY, + QGIS_SERVER_CACHE_SIZE + }; + Q_ENUM( EnvVar ) +}; + +/** \ingroup server + * QgsServerSettings provides a way to retrieve settings by prioritizing + * according to environment variables, ini file and default values. + * @note added in QGIS 3.0 + */ +class SERVER_EXPORT QgsServerSettings +{ + public: + struct Setting + { + QgsServerSettingsEnv::EnvVar envVar; + QgsServerSettingsEnv::Source src; + QString descr; + QString iniKey; + QVariant::Type type; + QVariant defaultVal; + QVariant val; + }; + + /** Constructor. + */ + QgsServerSettings(); + + /** Load settings according to current environment variables. + */ + void load(); + + /** Load setting for a specific environment variable name. + * @return true if loading is successful, false in case of an invalid name. + */ + bool load( const QString& envVarName ); + + /** Log a summary of settings curently loaded. + */ + void logSummary() const; + + /** Returns the ini file loaded by QSetting. + * @return the path of the ini file or an empty string if none is loaded. + */ + QString iniFile() const; + + /** Returns parallel rendering setting. + * @return true if parallel rendering is activated, false otherwise. + */ + bool parallelRendering() const; + + /** Returns the maximum number of threads to use. + * @return the number of threads. + */ + int maxThreads() const; + + /** + * Returns the maximum number of cached layers. + * @return the number of cached layers. + */ + int maxCacheLayers() const; + + /** Returns the log level. + * @return the log level. + */ + QgsMessageLog::MessageLevel logLevel() const; + + /** Returns the QGS project file to use. + * @return the path of the QGS project or an empty string if none is defined. + */ + QString projectFile() const; + + /** Returns the log file. + * @return the path of the log file or an empty string if none is defined. + */ + QString logFile() const; + + /** Returns the cache size. + * @return the cache size. + */ + qint64 cacheSize() const; + + /** Returns the cache directory. + * @return the directory. + */ + QString cacheDirectory() const; + + private: + void initSettings(); + QVariant value( QgsServerSettingsEnv::EnvVar envVar ) const; + QMap getEnv() const; + void loadQSettings( const QString& envOptPath ) const; + void prioritize( const QMap& env ); + + QMap< QgsServerSettingsEnv::EnvVar, Setting > mSettings; +}; + +#endif diff --git a/src/server/qgswcsserver.cpp b/src/server/qgswcsserver.cpp index 08dbe3b5de3..e40114b39b0 100644 --- a/src/server/qgswcsserver.cpp +++ b/src/server/qgswcsserver.cpp @@ -40,13 +40,15 @@ static const QString OGC_NAMESPACE = QStringLiteral( "http://www.opengis.net/ogc QgsWCSServer::QgsWCSServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap ¶meters , QgsWCSProjectParser* pp , QgsRequestHandler* rh - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ) : QgsOWSServer( configFilePath + , settings , parameters , rh , accessControl @@ -60,6 +62,7 @@ QgsWCSServer::QgsWCSServer( QgsWCSServer::QgsWCSServer() : QgsOWSServer( QString() + , QgsServerSettings() , QMap() , nullptr , nullptr diff --git a/src/server/qgswcsserver.h b/src/server/qgswcsserver.h index c5f1b72ce0c..0facebacb60 100644 --- a/src/server/qgswcsserver.h +++ b/src/server/qgswcsserver.h @@ -38,10 +38,11 @@ class QgsWCSServer: public QgsOWSServer //! Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership) QgsWCSServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap& parameters , QgsWCSProjectParser* pp , QgsRequestHandler* rh - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ); void executeRequest() override; diff --git a/src/server/qgswfsserver.cpp b/src/server/qgswfsserver.cpp index 0b008cb2872..c9bf2d7ce1e 100644 --- a/src/server/qgswfsserver.cpp +++ b/src/server/qgswfsserver.cpp @@ -64,13 +64,15 @@ static const QString QGS_NAMESPACE = QStringLiteral( "http://www.qgis.org/gml" ) QgsWfsServer::QgsWfsServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap ¶meters , QgsWfsProjectParser* cp , QgsRequestHandler* rh - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ) : QgsOWSServer( configFilePath + , settings , parameters , rh , accessControl @@ -83,6 +85,7 @@ QgsWfsServer::QgsWfsServer( QgsWfsServer::QgsWfsServer() : QgsOWSServer( QString() + , QgsServerSettings() , QMap() , nullptr , nullptr diff --git a/src/server/qgswfsserver.h b/src/server/qgswfsserver.h index 0d12db231e5..bec591bf95e 100644 --- a/src/server/qgswfsserver.h +++ b/src/server/qgswfsserver.h @@ -59,10 +59,11 @@ class QgsWfsServer: public QgsOWSServer //! Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership) QgsWfsServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap& parameters , QgsWfsProjectParser* cp , QgsRequestHandler* rh - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ); void executeRequest() override; diff --git a/src/server/services/wms/CMakeLists.txt b/src/server/services/wms/CMakeLists.txt index 12eef00a0cb..de1e75fd471 100644 --- a/src/server/services/wms/CMakeLists.txt +++ b/src/server/services/wms/CMakeLists.txt @@ -17,6 +17,7 @@ SET (wms_SRCS qgswmsgetstyle.cpp qgswmsgetstyles.cpp qgswmsservertransitional.cpp + qgsmaprendererjobproxy.cpp qgsfilterrestorer.cpp ) @@ -40,14 +41,14 @@ INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR}/src/analysis ${CMAKE_BINARY_DIR}/src/server ${CMAKE_CURRENT_BINARY_DIR} - ../../../core + ../../../core ../../../core/dxf - ../../../core/geometry + ../../../core/geometry ../../../core/raster ../../../core/symbology-ng ../../../core/composer ../../../core/layertree - ../../../gui + ../../../gui ../../../gui/editorwidgets ../../../gui/editorwidgets/core ../.. diff --git a/src/server/services/wms/qgsdxfwriter.cpp b/src/server/services/wms/qgsdxfwriter.cpp index ff2dc86ffaf..c8da485b5d6 100644 --- a/src/server/services/wms/qgsdxfwriter.cpp +++ b/src/server/services/wms/qgsdxfwriter.cpp @@ -122,7 +122,9 @@ namespace QgsWms QMap formatOptionsMap = parseFormatOptions( params.value( QStringLiteral( "FORMAT_OPTIONS" ) ) ); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), + params, configParser, serverIface->accessControls() ); diff --git a/src/server/services/wms/qgsmaprendererjobproxy.cpp b/src/server/services/wms/qgsmaprendererjobproxy.cpp new file mode 100644 index 00000000000..b03d36d8923 --- /dev/null +++ b/src/server/services/wms/qgsmaprendererjobproxy.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** + qgsmaprendererjobproxy.cpp + -------------------------- + begin : January 2017 + copyright : (C) 2017 by Paul Blottiere + email : paul dot blottiere at oslandia 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 "qgsmaprendererjobproxy.h" + +#include "qgsmessagelog.h" +#include "qgsmaprendererparalleljob.h" +#include "qgsmaprenderercustompainterjob.h" + +namespace QgsWms +{ + + QgsMapRendererJobProxy::QgsMapRendererJobProxy( + bool parallelRendering + , int maxThreads +#ifdef HAVE_SERVER_PYTHON_PLUGINS + , QgsAccessControl* accessControl +#endif + ) + : + mParallelRendering( parallelRendering ) +#ifdef HAVE_SERVER_PYTHON_PLUGINS + , mAccessControl( accessControl ) +#endif + { + if ( mParallelRendering ) + { + QgsApplication::setMaxThreads( maxThreads ); + QgsMessageLog::logMessage( QStringLiteral( "Parallel rendering activated with %1 threads" ).arg( maxThreads ), QStringLiteral( "server" ), QgsMessageLog::INFO ); + } + else + { + QgsMessageLog::logMessage( QStringLiteral( "Parallel rendering deactivated" ), QStringLiteral( "server" ), QgsMessageLog::INFO ); + } + } + + void QgsMapRendererJobProxy::render( const QgsMapSettings& mapSettings, QImage* image ) + { + if ( mParallelRendering ) + { + QgsMapRendererParallelJob renderJob( mapSettings ); +#ifdef HAVE_SERVER_PYTHON_PLUGINS + renderJob.setFeatureFilterProvider( mAccessControl ); +#endif + renderJob.start(); + renderJob.waitForFinished(); + *image = renderJob.renderedImage(); + mPainter.reset( new QPainter( image ) ); + } + else + { + mPainter.reset( new QPainter( image ) ); + QgsMapRendererCustomPainterJob renderJob( mapSettings, mPainter.data() ); +#ifdef HAVE_SERVER_PYTHON_PLUGINS + renderJob.setFeatureFilterProvider( mAccessControl ); +#endif + renderJob.renderSynchronously(); + } + } + + QPainter* QgsMapRendererJobProxy::takePainter() + { + return mPainter.take(); + } + +} // namespace qgsws diff --git a/src/server/services/wms/qgsmaprendererjobproxy.h b/src/server/services/wms/qgsmaprendererjobproxy.h new file mode 100644 index 00000000000..1307374b210 --- /dev/null +++ b/src/server/services/wms/qgsmaprendererjobproxy.h @@ -0,0 +1,68 @@ +/*************************************************************************** + qgsmaprendererjobproxy.h + ------------------------ + begin : January 2017 + copyright : (C) 2017 by Paul Blottiere + email : paul dot blottiere at oslandia 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 QGSMAPRENDERERJOBPROXY_H +#define QGSMAPRENDERERJOBPROXY_H + +#include "qgsmapsettings.h" +#include "qgsaccesscontrol.h" + +namespace QgsWms +{ + + /** \ingroup server + * thiss class provides a proxy for sequential or parallel map render job by + * reading qsettings. + * @note added in QGIS 3.0 + */ + class QgsMapRendererJobProxy + { + public: + + /** Constructor. + * @param accessControl Does not take ownership of QgsAccessControl + */ + QgsMapRendererJobProxy( + bool parallelRendering + , int maxThreads +#ifdef HAVE_SERVER_PYTHON_PLUGINS + , QgsAccessControl* accessControl +#endif + ); + + /** Sequential or parallel map rendering according to qsettings. + * @param mapSettings passed to MapRendererJob + * @param the rendered image + */ + void render( const QgsMapSettings& mapSettings, QImage* image ); + + /** Take ownership of the painter used for rendering. + * @return painter + */ + QPainter* takePainter(); + + private: + bool mParallelRendering; +#ifdef HAVE_SERVER_PYTHON_PLUGINS + QgsAccessControl* mAccessControl; +#endif + QScopedPointer mPainter; + }; + + +} +#endif diff --git a/src/server/services/wms/qgswmsdescribelayer.cpp b/src/server/services/wms/qgswmsdescribelayer.cpp index ac2235b16da..a8131a5284e 100644 --- a/src/server/services/wms/qgswmsdescribelayer.cpp +++ b/src/server/services/wms/qgswmsdescribelayer.cpp @@ -32,7 +32,9 @@ namespace QgsWms QgsServerRequest::Parameters params = request.parameters(); try { - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), + params, getConfigParser( serverIface ), serverIface->accessControls() ); QDomDocument doc = server.describeLayer(); diff --git a/src/server/services/wms/qgswmsgetcapabilities.cpp b/src/server/services/wms/qgswmsgetcapabilities.cpp index aa26f8090e5..fc2053e1a31 100644 --- a/src/server/services/wms/qgswmsgetcapabilities.cpp +++ b/src/server/services/wms/qgswmsgetcapabilities.cpp @@ -31,7 +31,8 @@ namespace QgsWms { QgsServerRequest::Parameters params = request.parameters(); QString configFilePath = serverIface->configFilePath(); - const QgsAccessControl* accessControl = serverIface->accessControls(); + QgsServerSettings* serverSettings = serverIface->serverSettings(); + QgsAccessControl* accessControl = serverIface->accessControls(); QgsCapabilitiesCache* capabilitiesCache = serverIface->capabilitiesCache(); QStringList cacheKeyList; @@ -50,7 +51,9 @@ namespace QgsWms QDomDocument doc; try { - QgsWmsServer server( configFilePath, params, + QgsWmsServer server( configFilePath, + *serverSettings, + params, getConfigParser( serverIface ), accessControl ); doc = server.getCapabilities( version, projectSettings ); diff --git a/src/server/services/wms/qgswmsgetcontext.cpp b/src/server/services/wms/qgswmsgetcontext.cpp index 2d0dc80db81..538f517dc60 100644 --- a/src/server/services/wms/qgswmsgetcontext.cpp +++ b/src/server/services/wms/qgswmsgetcontext.cpp @@ -32,7 +32,9 @@ namespace QgsWms try { Q_UNUSED( version ); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), + params, getConfigParser( serverIface ), serverIface->accessControls() ); QDomDocument doc = server.getContext(); diff --git a/src/server/services/wms/qgswmsgetfeatureinfo.cpp b/src/server/services/wms/qgswmsgetfeatureinfo.cpp index c64596d6177..bc34a4332bc 100644 --- a/src/server/services/wms/qgswmsgetfeatureinfo.cpp +++ b/src/server/services/wms/qgswmsgetfeatureinfo.cpp @@ -161,7 +161,8 @@ namespace QgsWms { Q_UNUSED( version ); QgsServerRequest::Parameters params = request.parameters(); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, getConfigParser( serverIface ), serverIface->accessControls() ); try diff --git a/src/server/services/wms/qgswmsgetlegendgraphics.cpp b/src/server/services/wms/qgswmsgetlegendgraphics.cpp index 0536036c3ee..b55e523c2c5 100644 --- a/src/server/services/wms/qgswmsgetlegendgraphics.cpp +++ b/src/server/services/wms/qgswmsgetlegendgraphics.cpp @@ -33,8 +33,8 @@ namespace QgsWms QgsServerRequest::Parameters params = request.parameters(); QgsWmsConfigParser* parser = getConfigParser( serverIface ); - QgsWmsServer server( serverIface->configFilePath(), params, parser, - serverIface->accessControls() ); + QgsWmsServer server( serverIface->configFilePath(), *serverIface->serverSettings(), + params, parser, serverIface->accessControls() ); try { QScopedPointer result( server.getLegendGraphics() ); diff --git a/src/server/services/wms/qgswmsgetmap.cpp b/src/server/services/wms/qgswmsgetmap.cpp index ffe4f85794e..a24d66f39b3 100644 --- a/src/server/services/wms/qgswmsgetmap.cpp +++ b/src/server/services/wms/qgswmsgetmap.cpp @@ -34,7 +34,8 @@ namespace QgsWms QgsServerRequest::Parameters params = request.parameters(); QgsWmsConfigParser* parser = getConfigParser( serverIface ); - QgsWmsServer server( serverIface->configFilePath(), params, parser, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, parser, serverIface->accessControls() ); try { diff --git a/src/server/services/wms/qgswmsgetprint.cpp b/src/server/services/wms/qgswmsgetprint.cpp index f8a52cf95ad..a957252afc5 100644 --- a/src/server/services/wms/qgswmsgetprint.cpp +++ b/src/server/services/wms/qgswmsgetprint.cpp @@ -31,7 +31,8 @@ namespace QgsWms try { Q_UNUSED( version ); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, getConfigParser( serverIface ), serverIface->accessControls() ); diff --git a/src/server/services/wms/qgswmsgetschemaextension.cpp b/src/server/services/wms/qgswmsgetschemaextension.cpp index 05e20126bf3..84e4e74555c 100644 --- a/src/server/services/wms/qgswmsgetschemaextension.cpp +++ b/src/server/services/wms/qgswmsgetschemaextension.cpp @@ -32,7 +32,8 @@ namespace QgsWms QgsServerRequest::Parameters params = request.parameters(); try { - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, getConfigParser( serverIface ), serverIface->accessControls() ); QDomDocument doc = server.getSchemaExtension(); diff --git a/src/server/services/wms/qgswmsgetstyle.cpp b/src/server/services/wms/qgswmsgetstyle.cpp index 04e44d51188..287fcb240f9 100644 --- a/src/server/services/wms/qgswmsgetstyle.cpp +++ b/src/server/services/wms/qgswmsgetstyle.cpp @@ -33,7 +33,8 @@ namespace QgsWms try { Q_UNUSED( version ); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, getConfigParser( serverIface ), serverIface->accessControls() ); QDomDocument doc = server.getStyle(); diff --git a/src/server/services/wms/qgswmsgetstyles.cpp b/src/server/services/wms/qgswmsgetstyles.cpp index d66ab0e2af1..32d819080e0 100644 --- a/src/server/services/wms/qgswmsgetstyles.cpp +++ b/src/server/services/wms/qgswmsgetstyles.cpp @@ -30,7 +30,8 @@ namespace QgsWms { Q_UNUSED( version ); QgsServerRequest::Parameters params = request.parameters(); - QgsWmsServer server( serverIface->configFilePath(), params, + QgsWmsServer server( serverIface->configFilePath(), + *serverIface->serverSettings(), params, getConfigParser( serverIface ), serverIface->accessControls() ); try diff --git a/src/server/services/wms/qgswmsservertransitional.cpp b/src/server/services/wms/qgswmsservertransitional.cpp index be3d8c39b32..7b9d15aff11 100644 --- a/src/server/services/wms/qgswmsservertransitional.cpp +++ b/src/server/services/wms/qgswmsservertransitional.cpp @@ -52,7 +52,7 @@ #include "qgseditorwidgetregistry.h" #include "qgsaccesscontrol.h" #include "qgsfeaturerequest.h" -#include "qgsmaprenderercustompainterjob.h" +#include "qgsmaprendererjobproxy.h" #include #include @@ -74,9 +74,10 @@ namespace QgsWms QgsWmsServer::QgsWmsServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap ¶meters , QgsWmsConfigParser* cp - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ) : mParameters( parameters ) , mOwnsConfigParser( false ) @@ -85,6 +86,7 @@ namespace QgsWms , mConfigParser( cp ) , mConfigFilePath( configFilePath ) , mAccessControl( accessControl ) + , mSettings( settings ) { } @@ -685,10 +687,9 @@ namespace QgsWms } - void QgsWmsServer::runHitTest( const QgsMapSettings& mapSettings, QPainter* painter, HitTest& hitTest ) + void QgsWmsServer::runHitTest( const QgsMapSettings& mapSettings, HitTest& hitTest ) { QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings ); - context.setPainter( painter ); // we are not going to draw anything, but we still need a working painter Q_FOREACH ( const QString& layerID, mapSettings.layerIds() ) { @@ -1099,9 +1100,6 @@ namespace QgsWms QStringList layersList, stylesList, layerIdList; QImage* theImage = initializeRendering( layersList, stylesList, layerIdList, mapSettings ); - QPainter thePainter( theImage ); - thePainter.setRenderHint( QPainter::Antialiasing ); //make it look nicer - QStringList layerSetIds = mapSettings.layerIds(); QStringList highlightLayersId = QgsWmsConfigParser::addHighlightLayers( mParameters, layerSetIds ); @@ -1142,36 +1140,41 @@ namespace QgsWms applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies ); + QScopedPointer painter; if ( hitTest ) - runHitTest( mapSettings, &thePainter, *hitTest ); + { + runHitTest( mapSettings, *hitTest ); + painter.reset( new QPainter() ); + } else { - QgsMapRendererCustomPainterJob renderJob( mapSettings, &thePainter ); #ifdef HAVE_SERVER_PYTHON_PLUGINS - renderJob.setFeatureFilterProvider( mAccessControl ); + mAccessControl->resolveFilterFeatures( mapSettings.layers() ); #endif - renderJob.renderSynchronously(); + QgsMapRendererJobProxy renderJob( mSettings.parallelRendering(), + mSettings.maxThreads() +#ifdef HAVE_SERVER_PYTHON_PLUGINS + , mAccessControl +#endif + ); + renderJob.render( mapSettings, theImage ); + painter.reset( renderJob.takePainter() ); } if ( mConfigParser ) { //draw configuration format specific overlay items - mConfigParser->drawOverlays( &thePainter, theImage->dotsPerMeterX() / 1000.0 * 25.4, theImage->width(), theImage->height() ); + mConfigParser->drawOverlays( painter.data(), theImage->dotsPerMeterX() / 1000.0 * 25.4, theImage->width(), theImage->height() ); } restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies ); clearFeatureSelections( selectedLayerIdList ); QgsWmsConfigParser::removeHighlightLayers( highlightLayersId ); - // QgsMessageLog::logMessage( "clearing filters" ); if ( !hitTest ) QgsProject::instance()->removeAllMapLayers(); - //#ifdef QGISDEBUG - // theImage->save( QDir::tempPath() + QDir::separator() + "lastrender.png" ); - //#endif - - thePainter.end(); + painter->end(); //test if width / height ratio of image is the same as the ratio of WIDTH / HEIGHT parameters. If not, the image has to be scaled (required by WMS spec) int widthParam = mParameters.value( "WIDTH", "0" ).toInt(); diff --git a/src/server/services/wms/qgswmsservertransitional.h b/src/server/services/wms/qgswmsservertransitional.h index 674bbca4a1f..12199cbe387 100644 --- a/src/server/services/wms/qgswmsservertransitional.h +++ b/src/server/services/wms/qgswmsservertransitional.h @@ -19,6 +19,7 @@ #define QGSWMSSERVER_H #include "qgswmsconfigparser.h" +#include "qgsserversettings.h" #include #include #include @@ -67,9 +68,10 @@ namespace QgsWms QgsConfigParser and QgsCapabilitiesCache*/ QgsWmsServer( const QString& configFilePath + , const QgsServerSettings& settings , QMap ¶meters , QgsWmsConfigParser* cp - , const QgsAccessControl* accessControl + , QgsAccessControl* accessControl ); ~QgsWmsServer(); @@ -190,7 +192,7 @@ namespace QgsWms QStringList layerSet( const QStringList& layersList, const QStringList& stylesList, const QgsCoordinateReferenceSystem& destCRS, double scaleDenominator = -1 ) const; //! Record which symbols would be used if the map was in the current configuration of renderer. This is useful for content-based legend - void runHitTest( const QgsMapSettings& mapSettings, QPainter* painter, HitTest& hitTest ); + void runHitTest( const QgsMapSettings& mapSettings, HitTest& hitTest ); //! Record which symbols within one layer would be rendered with the given renderer context void runHitTestLayer( QgsVectorLayer* vl, SymbolSet& usedSymbols, QgsRenderContext& context ); @@ -272,7 +274,9 @@ namespace QgsWms QgsWmsConfigParser* mConfigParser; QString mConfigFilePath; //! The access control helper - const QgsAccessControl* mAccessControl; + QgsAccessControl* mAccessControl; + + QgsServerSettings mSettings; public: QDomElement createFeatureGML( diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index b05838e378e..3d82a87f10e 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -166,6 +166,7 @@ ENDIF (WITH_APIDOC) IF (WITH_SERVER) ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py) + ADD_PYTHON_TEST(PyQgsServerSettings test_qgsserver_settings.py) ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py) ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py) ADD_PYTHON_TEST(PyQgsOfflineEditingWFS test_offline_editing_wfs.py) diff --git a/tests/src/python/test_qgsserver_settings.py b/tests/src/python/test_qgsserver_settings.py new file mode 100644 index 00000000000..9ec1e65b251 --- /dev/null +++ b/tests/src/python/test_qgsserver_settings.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsServerSettings. + +.. note:: 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. + +""" +__author__ = 'Paul Blottiere' +__date__ = '20/12/2016' +__copyright__ = 'Copyright 2016, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import os + +from qgis.PyQt.QtCore import QCoreApplication + +from utilities import unitTestDataPath +from qgis.testing import unittest +from qgis.core import QgsApplication +from qgis.server import QgsServerSettings + + +class TestQgsServerSettings(unittest.TestCase): + + def setUp(self): + self.settings = QgsServerSettings() + self.testdata_path = unitTestDataPath("qgis_server_settings") + + def tearDown(self): + pass + + def test_env_parallel_rendering(self): + env = "QGIS_SERVER_PARALLEL_RENDERING" + + # test parallel rendering value from environment variable + os.environ[env] = "1" + self.settings.load() + self.assertTrue(self.settings.parallelRendering()) + os.environ.pop(env) + + os.environ[env] = "0" + self.settings.load() + self.assertFalse(self.settings.parallelRendering()) + os.environ.pop(env) + + def test_env_log_level(self): + env = "QGIS_SERVER_LOG_LEVEL" + + # test log level value from environment variable + os.environ[env] = "3" + self.settings.load() + self.assertEqual(self.settings.logLevel(), 3) + os.environ.pop(env) + + os.environ[env] = "1" + self.settings.load() + self.assertEqual(self.settings.logLevel(), 1) + os.environ.pop(env) + + def test_env_log_file(self): + env = "QGIS_SERVER_LOG_FILE" + + # test parallel rendering value from environment variable + os.environ[env] = "/tmp/qgisserv.log" + self.settings.load() + self.assertEqual(self.settings.logFile(), "/tmp/qgisserv.log") + os.environ.pop(env) + + os.environ[env] = "/tmp/qserv.log" + self.settings.load() + self.assertEqual(self.settings.logFile(), "/tmp/qserv.log") + os.environ.pop(env) + + def test_env_project_file(self): + env = "QGIS_PROJECT_FILE" + + # test parallel rendering value from environment variable + os.environ[env] = "/tmp/myproject.qgs" + self.settings.load() + self.assertEqual(self.settings.projectFile(), "/tmp/myproject.qgs") + os.environ.pop(env) + + os.environ[env] = "/tmp/myproject2.qgs" + self.settings.load() + self.assertEqual(self.settings.projectFile(), "/tmp/myproject2.qgs") + os.environ.pop(env) + + def test_env_max_cache_layers(self): + env = "MAX_CACHE_LAYERS" + + # test parallel rendering value from environment variable + os.environ[env] = "3" + self.settings.load() + self.assertEqual(self.settings.maxCacheLayers(), 3) + os.environ.pop(env) + + os.environ[env] = "100" + self.settings.load() + self.assertEqual(self.settings.maxCacheLayers(), 100) + os.environ.pop(env) + + def test_env_max_threads(self): + env = "QGIS_SERVER_MAX_THREADS" + + # test parallel rendering value from environment variable + os.environ[env] = "3" + self.settings.load() + self.assertEqual(self.settings.maxThreads(), 3) + os.environ.pop(env) + + os.environ[env] = "5" + self.settings.load() + self.assertEqual(self.settings.maxThreads(), 5) + os.environ.pop(env) + + def test_env_cache_size(self): + env = "QGIS_SERVER_CACHE_SIZE" + + self.assertEqual(self.settings.cacheSize(), 50 * 1024 * 1024) + + os.environ[env] = "1024" + self.settings.load() + self.assertEqual(self.settings.cacheSize(), 1024) + os.environ.pop(env) + + def test_env_cache_directory(self): + env = "QGIS_SERVER_CACHE_DIRECTORY" + + os.environ[env] = "/tmp/fake" + self.settings.load() + self.assertEqual(self.settings.cacheDirectory(), "/tmp/fake") + os.environ.pop(env) + + def test_priority(self): + env = "QGIS_OPTIONS_PATH" + dpath = "conf0" + ini = "{0}.ini".format(os.path.join(self.testdata_path, dpath)) + QCoreApplication.setOrganizationName(dpath) + + # load settings + os.environ[env] = self.testdata_path + self.settings.load() + + # test conf + self.assertTrue(self.settings.parallelRendering()) + self.assertEqual(self.settings.maxThreads(), 3) + + # set environment variables and test priority + env_pr = "QGIS_SERVER_PARALLEL_RENDERING" + os.environ[env_pr] = "0" + + env_mt = "QGIS_SERVER_MAX_THREADS" + os.environ[env_mt] = "5" + + self.settings.load() + self.assertFalse(self.settings.parallelRendering()) + self.assertEqual(self.settings.maxThreads(), 5) + + # clear environment + os.environ.pop(env) + os.environ.pop(env_pr) + os.environ.pop(env_mt) + + def test_options_path_conf0(self): + env = "QGIS_OPTIONS_PATH" + dpath = "conf0" + ini = "{0}.ini".format(os.path.join(self.testdata_path, dpath)) + QCoreApplication.setOrganizationName(dpath) + + # load settings + os.environ[env] = self.testdata_path + self.settings.load() + + # test ini file + self.assertEqual(ini, self.settings.iniFile()) + + # test conf + self.assertTrue(self.settings.parallelRendering()) + self.assertEqual(self.settings.maxThreads(), 3) + self.assertEqual(self.settings.cacheSize(), 52428800) + + # default value when an empty string is indicated in ini file + self.assertEqual(self.settings.cacheDirectory(), "cache") + + # clear environment + os.environ.pop(env) + + def test_options_path_conf1(self): + env = "QGIS_OPTIONS_PATH" + dpath = "conf1" + ini = "{0}.ini".format(os.path.join(self.testdata_path, dpath)) + QCoreApplication.setOrganizationName(dpath) + + # load settings + os.environ[env] = self.testdata_path + self.settings.load() + + # test ini file + self.assertEqual(ini, self.settings.iniFile()) + + # test conf + self.assertFalse(self.settings.parallelRendering()) + self.assertEqual(self.settings.maxThreads(), 5) + self.assertEqual(self.settings.cacheSize(), 52428800) + self.assertEqual(self.settings.cacheDirectory(), "/tmp/mycache") + + # clear environment + os.environ.pop(env) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testdata/qgis_server_settings/conf0.ini b/tests/testdata/qgis_server_settings/conf0.ini new file mode 100644 index 00000000000..011717bf364 --- /dev/null +++ b/tests/testdata/qgis_server_settings/conf0.ini @@ -0,0 +1,7 @@ +[cache] +directory= +size=@Variant(\0\0\0\x81\0\0\0\0\x3 \0\0) + +[qgis] +parallel_rendering=true +max_threads=3 diff --git a/tests/testdata/qgis_server_settings/conf1.ini b/tests/testdata/qgis_server_settings/conf1.ini new file mode 100644 index 00000000000..6c40c580df5 --- /dev/null +++ b/tests/testdata/qgis_server_settings/conf1.ini @@ -0,0 +1,7 @@ +[cache] +directory=/tmp/mycache +size=@Variant(\0\0\0\x81\0\0\0\0\x3 \0\0) + +[qgis] +parallel_rendering=false +max_threads=5