diff --git a/python/server/qgsconfigcache.sip b/python/server/qgsconfigcache.sip index ba9c736134c..293739faec5 100644 --- a/python/server/qgsconfigcache.sip +++ b/python/server/qgsconfigcache.sip @@ -32,7 +32,6 @@ class QgsConfigCache: QObject ~QgsConfigCache(); QgsServerProjectParser* serverConfiguration( const QString& filePath ); - QgsWfsProjectParser* wfsConfiguration( const QString& filePath, const QgsAccessControl* accessControl ); QgsWmsConfigParser* wmsConfiguration( const QString& filePath, const QgsAccessControl* accessControl, const QMap& parameterMap = QMap< QString, QString >() ); private: diff --git a/python/server/qgsserverprojectutils.sip b/python/server/qgsserverprojectutils.sip index 8fa1ed5e480..7584fbb06d1 100644 --- a/python/server/qgsserverprojectutils.sip +++ b/python/server/qgsserverprojectutils.sip @@ -123,6 +123,30 @@ namespace QgsServerProjectUtils */ QString wfsServiceUrl( const QgsProject& project ); + /** Returns the Layer ids list defined in a QGIS project as published in WFS. + * @param project the QGIS project + * @return the Layer ids list. + */ + QStringList wfsLayerIds( const QgsProject& project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with update capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + QStringList wfstUpdateLayerIds( const QgsProject& project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with insert capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + QStringList wfstInsertLayerIds( const QgsProject& project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with delete capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + QStringList wfstDeleteLayerIds( const QgsProject& project ); + /** Returns the WCS service url defined in a QGIS project. * @param project the QGIS project * @return url if defined in project, an empty string otherwise. diff --git a/python/server/qgswfserver.sip b/python/server/qgswfserver.sip deleted file mode 100644 index 407fff05bb0..00000000000 --- a/python/server/qgswfserver.sip +++ /dev/null @@ -1,125 +0,0 @@ -/*************************************************************************** - qgswfsserver.sip - ------------------- - begin : May 2, 2015 - copyright : (C) 2015 by Alessandro Pasotti - email : a dot pasotti at itopen dot it - ***************************************************************************/ - -/*************************************************************************** - * * - * 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 QGSWFSSERVER_H -#define QGSWFSSERVER_H - -#include -#include -#include -#include -#include "qgis.h" -#include "qgsowsserver.h" -#include "qgsvectorlayer.h" -#include "qgswfsprojectparser.h" - -class QgsCoordinateReferenceSystem; -class QgsComposerLayerItem; -class QgsComposerLegendItem; -class QgsComposition; -class QgsFields; -class QgsMapLayer; -class QgsPoint; -class QgsRasterLayer; -class QgsConfigParser; -class QgsVectorLayer; -class QgsCoordinateReferenceSystem; -class QgsField; -class QgsFeature; -class QgsRectangle; -class QgsGeometry; -class QgsSymbol; -class QgsRequestHandler; -class QFile; -class QFont; -class QImage; -class QPaintDevice; -class QPainter; - - -/** This class handles all the wms server requests. The parameters and values have to be passed in the form of -a map. This map is usually generated by a subclass of QgsWMSRequestHandler, which makes QgsWFSServer -independent from any server side technology*/ - -class QgsWfsServer: public QgsOWSServer -{ - public: - /** 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 ); - ~QgsWfsServer(); - - void executeRequest() override; - - /** Returns an XML file with the capabilities description (as described in the WFS specs)*/ - QDomDocument getCapabilities(); - - /** Returns an XML file with the describe feature type (as described in the WFS specs)*/ - QDomDocument describeFeatureType(); - - /** Creates a document that describes the result of the getFeature request. - @return 0 in case of success*/ - int getFeature( QgsRequestHandler& request, const QString& format ); - - /** Read and apply the transaction - @return 0 in case of success*/ - QDomDocument transaction( const QString& requestBody ); - - /** Sets configuration parser for administration settings. Does not take ownership*/ - void setAdminConfigParser( QgsWfsProjectParser* parser ) { mConfigParser = parser; } - - private: - /** Don't use the default constructor*/ - QgsWfsServer(); - - /** Get service address from REQUEST_URI if not specified in the configuration*/ - QString serviceUrl() const; - - /* The Type of Feature created */ - QString mTypeName; - /* The list of Feature's Type requested */ - QStringList mTypeNames; - QString mPropertyName; - bool mWithGeom; - /* Error messages */ - QStringList mErrors; - - QgsWFSProjectParser* mConfigParser; - - protected: - - void startGetFeature( QgsRequestHandler& request, const QString& format, int prec, QgsCoordinateReferenceSystem& crs, QgsRectangle* rect ); - void setGetFeature( QgsRequestHandler& request, const QString& format, QgsFeature* feat, int featIdx, int prec, QgsCoordinateReferenceSystem& crs, const QgsAttributeList& attrIndexes, const QSet& excludedAttributes ); - void endGetFeature( QgsRequestHandler& request, const QString& format ); - - //method for transaction - QgsFeatureIds getFeatureIdsFromFilter( const QDomElement& filter, QgsVectorLayer* layer ); - - //methods to write GeoJSON - QString createFeatureGeoJSON( QgsFeature* feat, int prec, QgsCoordinateReferenceSystem& crs, const QgsAttributeList& attrIndexes, const QSet& excludedAttributes ) /*const*/; - - //methods to write GML2 - QDomElement createFeatureGML2( QgsFeature* feat, QDomDocument& doc, int prec, QgsCoordinateReferenceSystem& crs, const QgsAttributeList& attrIndexes, const QSet& excludedAttributes ) /*const*/; - - //methods to write GML3 - QDomElement createFeatureGML3( QgsFeature* feat, QDomDocument& doc, int prec, QgsCoordinateReferenceSystem& crs, QgsAttributeList attrIndexes, QSet excludedAttributes ) /*const*/; - - void addTransactionResult( QDomDocument& responseDoc, QDomElement& responseElem, const QString& status, const QString& locator, const QString& message ); -}; - -#endif diff --git a/python/server/server.sip b/python/server/server.sip index afa1a0d7194..9b3d8ec49f3 100644 --- a/python/server/server.sip +++ b/python/server/server.sip @@ -23,7 +23,6 @@ %Include qgsserverprojectparser.sip %Include qgswmsconfigparser.sip %Include qgswmsprojectparser.sip -%Include qgswfsprojectparser.sip %Include qgsconfigcache.sip %Include qgsserversettings.sip %Include qgsserverprojectutils.sip diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 27d83941f80..82367bca4ab 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -34,7 +34,6 @@ SET(qgis_mapserv_SRCS qgsremotedatasourcebuilder.cpp qgssentdatasourcebuilder.cpp qgsserverlogger.cpp - qgswfsprojectparser.cpp qgswmsconfigparser.cpp qgswmsprojectparser.cpp qgsserverprojectparser.cpp diff --git a/src/server/qgsconfigcache.cpp b/src/server/qgsconfigcache.cpp index d56addeb5b8..aeb1050c585 100644 --- a/src/server/qgsconfigcache.cpp +++ b/src/server/qgsconfigcache.cpp @@ -18,7 +18,6 @@ #include "qgsconfigcache.h" #include "qgsmessagelog.h" #include "qgsmslayercache.h" -#include "qgswfsprojectparser.h" #include "qgswmsprojectparser.h" #include "qgssldconfigparser.h" #include "qgsaccesscontrol.h" @@ -76,32 +75,6 @@ QgsServerProjectParser *QgsConfigCache::serverConfiguration( const QString &file return new QgsServerProjectParser( doc, filePath ); } -QgsWfsProjectParser *QgsConfigCache::wfsConfiguration( - const QString &filePath - , const QgsAccessControl *accessControl -) -{ - QgsWfsProjectParser *p = mWFSConfigCache.object( filePath ); - if ( !p ) - { - QDomDocument *doc = xmlDocument( filePath ); - if ( !doc ) - { - return nullptr; - } - p = new QgsWfsProjectParser( - filePath - , accessControl - ); - mWFSConfigCache.insert( filePath, p ); - p = mWFSConfigCache.object( filePath ); - Q_ASSERT( p ); - } - - QgsMSLayerCache::instance()->setProjectMaxLayers( p->wfsLayers().size() ); - return p; -} - QgsWmsConfigParser *QgsConfigCache::wmsConfiguration( const QString &filePath , const QgsAccessControl *accessControl @@ -182,7 +155,6 @@ QDomDocument *QgsConfigCache::xmlDocument( const QString &filePath ) void QgsConfigCache::removeChangedEntry( const QString &path ) { mWMSConfigCache.remove( path ); - mWFSConfigCache.remove( path ); //xml document must be removed last, as other config cache destructors may require it mXmlDocumentCache.remove( path ); diff --git a/src/server/qgsconfigcache.h b/src/server/qgsconfigcache.h index 4c1598b0825..4483cf9ae2d 100644 --- a/src/server/qgsconfigcache.h +++ b/src/server/qgsconfigcache.h @@ -27,7 +27,6 @@ #include "qgis_server.h" #include "qgswmsconfigparser.h" -#include "qgswfsprojectparser.h" class QgsServerProjectParser; class QgsAccessControl; @@ -41,10 +40,6 @@ class SERVER_EXPORT QgsConfigCache : public QObject static QgsConfigCache *instance(); QgsServerProjectParser *serverConfiguration( const QString &filePath ); - QgsWfsProjectParser *wfsConfiguration( - const QString &filePath - , const QgsAccessControl *accessControl - ); QgsWmsConfigParser *wmsConfiguration( const QString &filePath , const QgsAccessControl *accessControl @@ -64,7 +59,6 @@ class SERVER_EXPORT QgsConfigCache : public QObject QCache mXmlDocumentCache; QCache mWMSConfigCache; - QCache mWFSConfigCache; private slots: //! Removes changed entry from this cache diff --git a/src/server/qgsserverprojectutils.cpp b/src/server/qgsserverprojectutils.cpp index f95b376a75a..d56d25f9084 100644 --- a/src/server/qgsserverprojectutils.cpp +++ b/src/server/qgsserverprojectutils.cpp @@ -97,6 +97,31 @@ QString QgsServerProjectUtils::wfsServiceUrl( const QgsProject &project ) return project.readEntry( QStringLiteral( "WFSUrl" ), QStringLiteral( "/" ), "" ); } +QStringList QgsServerProjectUtils::wfsLayerIds( const QgsProject &project ) +{ + return project.readListEntry( QStringLiteral( "WFSLayers" ), QStringLiteral( "/" ) ); +} + +int QgsServerProjectUtils::wfsLayerPrecision( const QString &layerId, const QgsProject &project ) +{ + return project.readNumEntry( QStringLiteral( "WFSLayersPrecision" ), "/" + layerId, 6 ); +} + +QStringList QgsServerProjectUtils::wfstUpdateLayerIds( const QgsProject &project ) +{ + return project.readListEntry( QStringLiteral( "WFSTLayers" ), QStringLiteral( "Update" ) ); +} + +QStringList QgsServerProjectUtils::wfstInsertLayerIds( const QgsProject &project ) +{ + return project.readListEntry( QStringLiteral( "WFSTLayers" ), QStringLiteral( "Insert" ) ); +} + +QStringList QgsServerProjectUtils::wfstDeleteLayerIds( const QgsProject &project ) +{ + return project.readListEntry( QStringLiteral( "WFSTLayers" ), QStringLiteral( "Delete" ) ); +} + QString QgsServerProjectUtils::wcsServiceUrl( const QgsProject &project ) { return project.readEntry( QStringLiteral( "WCSUrl" ), QStringLiteral( "/" ), "" ); diff --git a/src/server/qgsserverprojectutils.h b/src/server/qgsserverprojectutils.h index 5c1c5a5dfd4..266b97908c1 100644 --- a/src/server/qgsserverprojectutils.h +++ b/src/server/qgsserverprojectutils.h @@ -125,6 +125,32 @@ namespace QgsServerProjectUtils */ SERVER_EXPORT QString wfsServiceUrl( const QgsProject &project ); + /** Returns the Layer ids list defined in a QGIS project as published in WFS. + * @param project the QGIS project + * @return the Layer ids list. + */ + SERVER_EXPORT QStringList wfsLayerIds( const QgsProject &project ); + + SERVER_EXPORT int wfsLayerPrecision( const QString &layerId, const QgsProject &project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with update capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + SERVER_EXPORT QStringList wfstUpdateLayerIds( const QgsProject &project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with insert capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + SERVER_EXPORT QStringList wfstInsertLayerIds( const QgsProject &project ); + + /** Returns the Layer ids list defined in a QGIS project as published as WFS-T with delete capabilities. + * @param project the QGIS project + * @return the Layer ids list. + */ + SERVER_EXPORT QStringList wfstDeleteLayerIds( const QgsProject &project ); + /** Returns the WCS service url defined in a QGIS project. * \param project the QGIS project * \returns url if defined in project, an empty string otherwise. diff --git a/src/server/qgswfsprojectparser.cpp b/src/server/qgswfsprojectparser.cpp deleted file mode 100644 index a8e774266c3..00000000000 --- a/src/server/qgswfsprojectparser.cpp +++ /dev/null @@ -1,572 +0,0 @@ -/*************************************************************************** - qgswfsprojectparser.cpp - ----------------------- - begin : March 25, 2014 - copyright : (C) 2014 by Marco Hugentobler - email : marco dot hugentobler at sourcepole dot ch - ***************************************************************************/ - -/*************************************************************************** - * * - * 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 "qgswfsprojectparser.h" -#include "qgsconfigcache.h" -#include "qgsconfigparserutils.h" -#include "qgsproject.h" -#include "qgsvectordataprovider.h" -#include "qgsmapserviceexception.h" -#include "qgsaccesscontrol.h" -#include "qgslogger.h" - - -QgsWfsProjectParser::QgsWfsProjectParser( - const QString &filePath - , const QgsAccessControl *ac -) - : mAccessControl( ac ) -{ -#ifndef HAVE_SERVER_PYTHON_PLUGINS - Q_UNUSED( mAccessControl ); -#endif - mProjectParser = QgsConfigCache::instance()->serverConfiguration( filePath ); -} - -QgsWfsProjectParser::~QgsWfsProjectParser() -{ - delete mProjectParser; -} - -void QgsWfsProjectParser::serviceCapabilities( QDomElement &parentElement, QDomDocument &doc ) const -{ - mProjectParser->serviceCapabilities( parentElement, doc, QStringLiteral( "WFS" ) ); -} - -void QgsWfsProjectParser::featureTypeList( QDomElement &parentElement, QDomDocument &doc ) const -{ - const QList &projectLayerElements = mProjectParser->projectLayerElements(); - if ( projectLayerElements.size() < 1 ) - { - return; - } - - QStringList wfsLayersId = mProjectParser->wfsLayers(); - QSet wfstUpdateLayersId = wfstUpdateLayers(); - QSet wfstInsertLayersId = wfstInsertLayers(); - QSet wfstDeleteLayersId = wfstDeleteLayers(); - - QMap layerMap; - - Q_FOREACH ( const QDomElement &elem, projectLayerElements ) - { - QString type = elem.attribute( QStringLiteral( "type" ) ); - if ( type == QLatin1String( "vector" ) ) - { - QString layerId = mProjectParser->layerId( elem ); - if ( !wfsLayersId.contains( layerId ) ) - { - continue; - } - QgsMapLayer *layer = mProjectParser->createLayerFromElement( elem ); - if ( !layer ) - { - continue; - } -#ifdef HAVE_SERVER_PYTHON_PLUGINS - if ( !mAccessControl->layerReadPermission( layer ) ) - { - continue; - } -#endif - QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) ); - layerMap.insert( layer->id(), layer ); - - QDomElement layerElem = doc.createElement( QStringLiteral( "FeatureType" ) ); - QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) ); - //We use the layer name even though it might not be unique. - //Because the id sometimes contains user/pw information and the name is more descriptive - QString typeName = layer->name(); - if ( !layer->shortName().isEmpty() ) - typeName = layer->shortName(); - typeName = typeName.replace( QLatin1String( " " ), QLatin1String( "_" ) ); - QDomText nameText = doc.createTextNode( typeName ); - nameElem.appendChild( nameText ); - layerElem.appendChild( nameElem ); - - QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) ); - QString titleName = layer->title(); - if ( titleName.isEmpty() ) - { - titleName = layer->name(); - } - QDomText titleText = doc.createTextNode( titleName ); - titleElem.appendChild( titleText ); - layerElem.appendChild( titleElem ); - - QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) ); - QString abstractName = layer->abstract(); - if ( abstractName.isEmpty() ) - { - abstractName = QLatin1String( "" ); - } - QDomText abstractText = doc.createTextNode( abstractName ); - abstractElem.appendChild( abstractText ); - layerElem.appendChild( abstractElem ); - - //keyword list - if ( !layer->keywordList().isEmpty() ) - { - QDomElement keywordsElem = doc.createElement( QStringLiteral( "Keywords" ) ); - QDomText keywordsText = doc.createTextNode( layer->keywordList() ); - keywordsElem.appendChild( keywordsText ); - layerElem.appendChild( keywordsElem ); - } - - //appendExGeographicBoundingBox( layerElem, doc, layer->extent(), layer->crs() ); - - QDomElement srsElem = doc.createElement( QStringLiteral( "SRS" ) ); - QDomText srsText = doc.createTextNode( layer->crs().authid() ); - srsElem.appendChild( srsText ); - layerElem.appendChild( srsElem ); - - //wfs:Operations element - QDomElement operationsElement = doc.createElement( QStringLiteral( "Operations" )/*wfs:Operations*/ ); - //wfs:Query element - QDomElement queryElement = doc.createElement( QStringLiteral( "Query" )/*wfs:Query*/ ); - operationsElement.appendChild( queryElement ); - - QgsVectorLayer *vlayer = qobject_cast( layer ); - QgsVectorDataProvider *provider = vlayer->dataProvider(); - if ( ( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) && wfstInsertLayersId.contains( layer->id() ) ) - { - //wfs:Insert element - QDomElement insertElement = doc.createElement( QStringLiteral( "Insert" )/*wfs:Insert*/ ); - operationsElement.appendChild( insertElement ); - } - if ( ( provider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) && - ( provider->capabilities() & QgsVectorDataProvider::ChangeGeometries ) && - wfstUpdateLayersId.contains( layer->id() ) ) - { - //wfs:Update element - QDomElement updateElement = doc.createElement( QStringLiteral( "Update" )/*wfs:Update*/ ); - operationsElement.appendChild( updateElement ); - } - if ( ( provider->capabilities() & QgsVectorDataProvider::DeleteFeatures ) && wfstDeleteLayersId.contains( layer->id() ) ) - { - //wfs:Delete element - QDomElement deleteElement = doc.createElement( QStringLiteral( "Delete" )/*wfs:Delete*/ ); - operationsElement.appendChild( deleteElement ); - } - - layerElem.appendChild( operationsElement ); - - QgsRectangle layerExtent = layer->extent(); - QDomElement bBoxElement = doc.createElement( QStringLiteral( "LatLongBoundingBox" ) ); - bBoxElement.setAttribute( QStringLiteral( "minx" ), QString::number( layerExtent.xMinimum() ) ); - bBoxElement.setAttribute( QStringLiteral( "miny" ), QString::number( layerExtent.yMinimum() ) ); - bBoxElement.setAttribute( QStringLiteral( "maxx" ), QString::number( layerExtent.xMaximum() ) ); - bBoxElement.setAttribute( QStringLiteral( "maxy" ), QString::number( layerExtent.yMaximum() ) ); - layerElem.appendChild( bBoxElement ); - - // layer metadata URL - QString metadataUrl = layer->metadataUrl(); - if ( !metadataUrl.isEmpty() ) - { - QDomElement metaUrlElem = doc.createElement( QStringLiteral( "MetadataURL" ) ); - QString metadataUrlType = layer->metadataUrlType(); - metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType ); - QString metadataUrlFormat = layer->metadataUrlFormat(); - if ( metadataUrlFormat == QLatin1String( "text/xml" ) ) - { - metaUrlElem.setAttribute( QStringLiteral( "format" ), QStringLiteral( "XML" ) ); - } - else - { - metaUrlElem.setAttribute( QStringLiteral( "format" ), QStringLiteral( "TXT" ) ); - } - QDomText metaUrlText = doc.createTextNode( metadataUrl ); - metaUrlElem.appendChild( metaUrlText ); - layerElem.appendChild( metaUrlElem ); - } - - parentElement.appendChild( layerElem ); - } - } - return; -} - -QSet QgsWfsProjectParser::wfstUpdateLayers() const -{ - QSet publishedIds = wfsLayerSet(); - QSet wfsList; - if ( !mProjectParser->xmlDocument() ) - { - return wfsList; - } - - QDomElement propertiesElem = mProjectParser->propertiesElem(); - if ( propertiesElem.isNull() ) - { - return wfsList; - } - QDomElement wfstLayersElem = propertiesElem.firstChildElement( QStringLiteral( "WFSTLayers" ) ); - if ( wfstLayersElem.isNull() ) - { - return wfsList; - } - QDomElement wfstUpdateLayersElem = wfstLayersElem.firstChildElement( QStringLiteral( "Update" ) ); - if ( wfstUpdateLayersElem.isNull() ) - { - return wfsList; - } - QDomNodeList valueList = wfstUpdateLayersElem.elementsByTagName( QStringLiteral( "value" ) ); - for ( int i = 0; i < valueList.size(); ++i ) - { - QString id = valueList.at( i ).toElement().text(); - if ( publishedIds.contains( id ) ) - { - wfsList.insert( id ); - } - } - return wfsList; -} - -QSet QgsWfsProjectParser::wfstInsertLayers() const -{ - QSet publishedIds = wfsLayerSet(); - QSet wfsList; - if ( !mProjectParser->xmlDocument() ) - { - return wfsList; - } - - QDomElement propertiesElem = mProjectParser->propertiesElem(); - if ( propertiesElem.isNull() ) - { - return wfsList; - } - QDomElement wfstLayersElem = propertiesElem.firstChildElement( QStringLiteral( "WFSTLayers" ) ); - if ( wfstLayersElem.isNull() ) - { - return wfsList; - } - QDomElement wfstInsertLayersElem = wfstLayersElem.firstChildElement( QStringLiteral( "Insert" ) ); - if ( wfstInsertLayersElem.isNull() ) - { - return wfsList; - } - QDomNodeList valueList = wfstInsertLayersElem.elementsByTagName( QStringLiteral( "value" ) ); - for ( int i = 0; i < valueList.size(); ++i ) - { - QString id = valueList.at( i ).toElement().text(); - if ( publishedIds.contains( id ) ) - { - wfsList.insert( id ); - } - } - return wfsList; -} - -QSet QgsWfsProjectParser::wfstDeleteLayers() const -{ - QSet publishedIds = wfsLayerSet(); - QSet wfsList; - if ( !mProjectParser->xmlDocument() ) - { - return wfsList; - } - - QDomElement propertiesElem = mProjectParser->propertiesElem(); - if ( propertiesElem.isNull() ) - { - return wfsList; - } - QDomElement wfstLayersElem = propertiesElem.firstChildElement( QStringLiteral( "WFSTLayers" ) ); - if ( wfstLayersElem.isNull() ) - { - return wfsList; - } - QDomElement wfstDeleteLayersElem = wfstLayersElem.firstChildElement( QStringLiteral( "Delete" ) ); - if ( wfstDeleteLayersElem.isNull() ) - { - return wfsList; - } - QDomNodeList valueList = wfstDeleteLayersElem.elementsByTagName( QStringLiteral( "value" ) ); - for ( int i = 0; i < valueList.size(); ++i ) - { - QString id = valueList.at( i ).toElement().text(); - if ( publishedIds.contains( id ) ) - { - wfsList.insert( id ); - } - } - return wfsList; -} - -void QgsWfsProjectParser::describeFeatureType( const QString &aTypeName, QDomElement &parentElement, QDomDocument &doc ) const -{ - const QList &projectLayerElements = mProjectParser->projectLayerElements(); - if ( projectLayerElements.size() < 1 ) - { - return; - } - - QStringList wfsLayersId = mProjectParser->wfsLayers(); - QStringList typeNameList; - if ( aTypeName != QLatin1String( "" ) ) - { - QStringList typeNameSplit = aTypeName.split( QStringLiteral( "," ) ); - Q_FOREACH ( const QString &str, typeNameSplit ) - { - if ( str.contains( QLatin1String( ":" ) ) ) - typeNameList << str.section( QStringLiteral( ":" ), 1, 1 ); - else - typeNameList << str; - } - } - - Q_FOREACH ( const QDomElement &elem, projectLayerElements ) - { - QString type = elem.attribute( QStringLiteral( "type" ) ); - if ( type == QLatin1String( "vector" ) ) - { - QgsMapLayer *mLayer = mProjectParser->createLayerFromElement( elem ); - QgsVectorLayer *layer = qobject_cast( mLayer ); - if ( !layer ) - continue; - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - if ( !mAccessControl->layerReadPermission( layer ) ) - { - continue; - } -#endif - - QString typeName = layer->name(); - if ( !layer->shortName().isEmpty() ) - typeName = layer->shortName(); - typeName = typeName.replace( QLatin1String( " " ), QLatin1String( "_" ) ); - - if ( wfsLayersId.contains( layer->id() ) && ( aTypeName == QLatin1String( "" ) || typeNameList.contains( typeName ) ) ) - { - //do a select with searchRect and go through all the features - QgsVectorDataProvider *provider = layer->dataProvider(); - if ( !provider ) - { - continue; - } - - //hidden attributes for this layer - const QSet &layerExcludedAttributes = layer->excludeAttributesWfs(); - - //xsd:element - QDomElement elementElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); - elementElem.setAttribute( QStringLiteral( "name" ), typeName ); - elementElem.setAttribute( QStringLiteral( "type" ), "qgs:" + typeName + "Type" ); - elementElem.setAttribute( QStringLiteral( "substitutionGroup" ), QStringLiteral( "gml:_Feature" ) ); - parentElement.appendChild( elementElem ); - - //xsd:complexType - QDomElement complexTypeElem = doc.createElement( QStringLiteral( "complexType" )/*xsd:complexType*/ ); - complexTypeElem.setAttribute( QStringLiteral( "name" ), typeName + "Type" ); - parentElement.appendChild( complexTypeElem ); - - //xsd:complexType - QDomElement complexContentElem = doc.createElement( QStringLiteral( "complexContent" )/*xsd:complexContent*/ ); - complexTypeElem.appendChild( complexContentElem ); - - //xsd:extension - QDomElement extensionElem = doc.createElement( QStringLiteral( "extension" )/*xsd:extension*/ ); - extensionElem.setAttribute( QStringLiteral( "base" ), QStringLiteral( "gml:AbstractFeatureType" ) ); - complexContentElem.appendChild( extensionElem ); - - //xsd:sequence - QDomElement sequenceElem = doc.createElement( QStringLiteral( "sequence" )/*xsd:sequence*/ ); - extensionElem.appendChild( sequenceElem ); - - //xsd:element - if ( layer->hasGeometryType() ) - { - QDomElement geomElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); - geomElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) ); - if ( provider->name() == QLatin1String( "ogr" ) ) - { - // because some ogr drivers (e.g. ESRI ShapeFile, GML) - // are not able to determine the geometry type of a layer. - // we set to GeometryType - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:GeometryPropertyType" ) ); - } - else - { - QgsWkbTypes::Type wkbType = layer->wkbType(); - switch ( wkbType ) - { - case QgsWkbTypes::Point25D: - case QgsWkbTypes::Point: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:PointPropertyType" ) ); - break; - case QgsWkbTypes::LineString25D: - case QgsWkbTypes::LineString: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:LineStringPropertyType" ) ); - break; - case QgsWkbTypes::Polygon25D: - case QgsWkbTypes::Polygon: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:PolygonPropertyType" ) ); - break; - case QgsWkbTypes::MultiPoint25D: - case QgsWkbTypes::MultiPoint: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiPointPropertyType" ) ); - break; - case QgsWkbTypes::MultiLineString25D: - case QgsWkbTypes::MultiLineString: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiLineStringPropertyType" ) ); - break; - case QgsWkbTypes::MultiPolygon25D: - case QgsWkbTypes::MultiPolygon: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiPolygonPropertyType" ) ); - break; - default: - geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:GeometryPropertyType" ) ); - break; - } - } - geomElem.setAttribute( QStringLiteral( "minOccurs" ), QStringLiteral( "0" ) ); - geomElem.setAttribute( QStringLiteral( "maxOccurs" ), QStringLiteral( "1" ) ); - sequenceElem.appendChild( geomElem ); - } - - //const QgsFields& fields = provider->fields(); - const QgsFields &fields = layer->pendingFields(); - for ( int idx = 0; idx < fields.count(); ++idx ) - { - - QString attributeName = fields.at( idx ).name(); - //skip attribute if excluded from WFS publication - if ( layerExcludedAttributes.contains( attributeName ) ) - { - continue; - } - - //xsd:element - QDomElement attElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); - attElem.setAttribute( QStringLiteral( "name" ), attributeName ); - QVariant::Type attributeType = fields.at( idx ).type(); - if ( attributeType == QVariant::Int ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "integer" ) ); - else if ( attributeType == QVariant::LongLong ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "long" ) ); - else if ( attributeType == QVariant::Double ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "double" ) ); - else if ( attributeType == QVariant::Bool ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "boolean" ) ); - else if ( attributeType == QVariant::Date ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "date" ) ); - else if ( attributeType == QVariant::Time ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "time" ) ); - else if ( attributeType == QVariant::DateTime ) - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "dateTime" ) ); - else - attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "string" ) ); - - sequenceElem.appendChild( attElem ); - - QString alias = fields.at( idx ).alias(); - if ( !alias.isEmpty() ) - { - attElem.setAttribute( QStringLiteral( "alias" ), alias ); - } - } - } - } - } - QgsProject::instance()->removeAllMapLayers(); - return; -} - -QStringList QgsWfsProjectParser::wfsLayers() const -{ - return mProjectParser->wfsLayers(); -} - -QSet QgsWfsProjectParser::wfsLayerSet() const -{ - return QSet::fromList( wfsLayers() ); -} - -int QgsWfsProjectParser::wfsLayerPrecision( const QString &aLayerId ) const -{ - QStringList wfsLayersId = mProjectParser->wfsLayers(); - if ( !wfsLayersId.contains( aLayerId ) ) - { - return -1; - } - int prec = 8; - QDomElement propertiesElem = mProjectParser->propertiesElem(); - if ( !propertiesElem.isNull() ) - { - QDomElement wfsPrecElem = propertiesElem.firstChildElement( QStringLiteral( "WFSLayersPrecision" ) ); - if ( !wfsPrecElem.isNull() ) - { - QDomElement wfsLayerPrecElem = wfsPrecElem.firstChildElement( aLayerId ); - if ( !wfsLayerPrecElem.isNull() ) - { - QString precStr = wfsLayerPrecElem.text(); - prec = precStr.toInt(); - } - } - } - return prec; -} - -QList QgsWfsProjectParser::mapLayerFromTypeName( const QString &aTypeName, bool useCache ) const -{ - Q_UNUSED( useCache ); - - QList layerList; - const QList &projectLayerElements = mProjectParser->projectLayerElements(); - - if ( projectLayerElements.size() < 1 ) - { - return layerList; - } - QStringList wfsLayersId = wfsLayers(); - - QStringList typeNameList; - if ( aTypeName != QLatin1String( "" ) ) - { - QStringList typeNameSplit = aTypeName.split( QStringLiteral( "," ) ); - Q_FOREACH ( const QString &str, typeNameSplit ) - { - if ( str.contains( QLatin1String( ":" ) ) ) - typeNameList << str.section( QStringLiteral( ":" ), 1, 1 ); - else - typeNameList << str; - } - } - - Q_FOREACH ( const QDomElement &elem, projectLayerElements ) - { - QString type = elem.attribute( QStringLiteral( "type" ) ); - if ( type == QLatin1String( "vector" ) ) - { - QgsMapLayer *mLayer = mProjectParser->createLayerFromElement( elem ); - QgsVectorLayer *layer = qobject_cast( mLayer ); - if ( !layer ) - continue; - - QString typeName = layer->name(); - if ( !layer->shortName().isEmpty() ) - typeName = layer->shortName(); - typeName = typeName.replace( QLatin1String( " " ), QLatin1String( "_" ) ); - - if ( wfsLayersId.contains( layer->id() ) && ( aTypeName == QLatin1String( "" ) || typeNameList.contains( typeName ) ) ) - layerList.push_back( mLayer ); - } - } - return layerList; -} diff --git a/src/server/qgswfsprojectparser.h b/src/server/qgswfsprojectparser.h deleted file mode 100644 index d5fad3c0008..00000000000 --- a/src/server/qgswfsprojectparser.h +++ /dev/null @@ -1,61 +0,0 @@ -/*************************************************************************** - qgswfsprojectparser.h - --------------------- - begin : March 25, 2014 - copyright : (C) 2014 by Marco Hugentobler - email : marco dot hugentobler at sourcepole dot ch - ***************************************************************************/ - -/*************************************************************************** - * * - * 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 QGSWFSPROJECTPARSER_H -#define QGSWFSPROJECTPARSER_H - -#include "qgsserverprojectparser.h" -#include "qgis_server.h" - - -class QgsAccessControl; - - -class SERVER_EXPORT QgsWfsProjectParser -{ - public: - QgsWfsProjectParser( - const QString &filePath - , const QgsAccessControl *ac - ); - ~QgsWfsProjectParser(); - - QgsWfsProjectParser( const QgsWfsProjectParser &rh ) = delete; - QgsWfsProjectParser &operator=( const QgsWfsProjectParser &rh ) = delete; - - void serviceCapabilities( QDomElement &parentElement, QDomDocument &doc ) const; - void featureTypeList( QDomElement &parentElement, QDomDocument &doc ) const; - - void describeFeatureType( const QString &aTypeName, QDomElement &parentElement, QDomDocument &doc ) const; - - QStringList wfsLayers() const; - QSet wfsLayerSet() const; - int wfsLayerPrecision( const QString &aLayerId ) const; - - QList mapLayerFromTypeName( const QString &aTypeName, bool useCache = true ) const; - - QSet wfstUpdateLayers() const; - QSet wfstInsertLayers() const; - QSet wfstDeleteLayers() const; - - private: - QgsServerProjectParser *mProjectParser = nullptr; - const QgsAccessControl *mAccessControl = nullptr; - -}; - -#endif // QGSWFSPROJECTPARSER_H diff --git a/src/server/services/wfs/qgswfs.cpp b/src/server/services/wfs/qgswfs.cpp index 9243258f052..29ef15dadc6 100644 --- a/src/server/services/wfs/qgswfs.cpp +++ b/src/server/services/wfs/qgswfs.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler ( parts fron qgswmshandler) + (C) 2012 by René-Luc D'Hont ( parts fron qgswmshandler) (C) 2014 by Alessandro Pasotti ( parts from qgswmshandler) (C) 2016 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -78,11 +79,11 @@ namespace QgsWfs } else if ( QSTR_COMPARE( req, "DescribeFeatureType" ) ) { - writeDescribeFeatureType( mServerIface, versionString, request, response ); + writeDescribeFeatureType( mServerIface, project, versionString, request, response ); } else if ( QSTR_COMPARE( req, "Transaction" ) ) { - writeTransaction( mServerIface, versionString, request, response ); + writeTransaction( mServerIface, project, versionString, request, response ); } else { diff --git a/src/server/services/wfs/qgswfsdescribefeaturetype.cpp b/src/server/services/wfs/qgswfsdescribefeaturetype.cpp index 68e5384d0a0..d620d320cac 100644 --- a/src/server/services/wfs/qgswfsdescribefeaturetype.cpp +++ b/src/server/services/wfs/qgswfsdescribefeaturetype.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -19,31 +20,42 @@ * * ***************************************************************************/ #include "qgswfsutils.h" +#include "qgsserverprojectutils.h" #include "qgswfsdescribefeaturetype.h" +#include "qgsproject.h" +#include "qgscsexception.h" +#include "qgsvectorlayer.h" +#include "qgsvectordataprovider.h" +#include "qgsmapserviceexception.h" +#include "qgscoordinatereferencesystem.h" + +#include + namespace QgsWfs { - void writeDescribeFeatureType( QgsServerInterface *serverIface, const QString &version, + void writeDescribeFeatureType( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ) { - QDomDocument doc = createDescribeFeatureTypeDocument( serverIface, version, request ); + QDomDocument doc = createDescribeFeatureTypeDocument( serverIface, project, version, request ); response.setHeader( "Content-Type", "text/xml; charset=utf-8" ); response.write( doc.toByteArray() ); } - QDomDocument createDescribeFeatureTypeDocument( QgsServerInterface *serverIface, const QString &version, + QDomDocument createDescribeFeatureTypeDocument( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request ) { Q_UNUSED( version ); QDomDocument doc; - QgsWfsProjectParser *configParser = getConfigParser( serverIface ); QgsServerRequest::Parameters parameters = request.parameters(); + QgsAccessControl *accessControl = serverIface->accessControls(); + //xsd:schema QDomElement schemaElement = doc.createElement( QStringLiteral( "schema" )/*xsd:schema*/ ); schemaElement.setAttribute( QStringLiteral( "xmlns" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema" ) ); @@ -62,9 +74,7 @@ namespace QgsWfs importElement.setAttribute( QStringLiteral( "schemaLocation" ), QStringLiteral( "http://schemas.opengis.net/gml/2.1.2/feature.xsd" ) ); schemaElement.appendChild( importElement ); - //defining typename - QString typeName = QLatin1String( "" ); - + QStringList typeNameList; QDomDocument queryDoc; QString errorMsg; if ( queryDoc.setContent( parameters.value( QStringLiteral( "REQUEST_BODY" ) ), true, &errorMsg ) ) @@ -79,23 +89,208 @@ namespace QgsWfs QDomElement docChildElem = docChildNodes.at( i ).toElement(); if ( docChildElem.tagName() == QLatin1String( "TypeName" ) ) { - if ( typeName == QLatin1String( "" ) ) - typeName = docChildElem.text(); + QString typeName = docChildElem.text().trimmed(); + if ( typeName.contains( QLatin1String( ":" ) ) ) + typeNameList << typeName.section( QStringLiteral( ":" ), 1, 1 ); else - typeName += "," + docChildElem.text(); + typeNameList << typeName; } } } } else { - typeName = request.getParameter( QStringLiteral( "TYPENAME" ) ); + QString typeNames = request.getParameter( QStringLiteral( "TYPENAME" ) ); + if ( !typeNames.isEmpty() ) + { + QStringList typeNameSplit = typeNames.split( QStringLiteral( "," ) ); + for ( int i = 0; i < typeNameSplit.size(); ++i ) + { + QString typeName = typeNameSplit.at( i ).trimmed(); + if ( typeName.contains( QLatin1String( ":" ) ) ) + typeNameList << typeName.section( QStringLiteral( ":" ), 1, 1 ); + else + typeNameList << typeName; + } + } } - configParser->describeFeatureType( typeName, schemaElement, doc ); + QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + for ( int i = 0; i < wfsLayerIds.size(); ++i ) + { + QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) ); + if ( layer->type() != QgsMapLayer::LayerType::VectorLayer ) + { + continue; + } + + QString name = layer->name(); + if ( !layer->shortName().isEmpty() ) + name = layer->shortName(); + name = name.replace( QLatin1String( " " ), QLatin1String( "_" ) ); + + if ( !typeNameList.isEmpty() && !typeNameList.contains( name ) ) + { + continue; + } + + if ( accessControl && !accessControl->layerReadPermission( layer ) ) + { + if ( !typeNameList.isEmpty() ) + { + throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) ); + } + else + { + continue; + } + } + + QgsVectorLayer *vLayer = qobject_cast( layer ); + QgsVectorDataProvider *provider = vLayer->dataProvider(); + if ( !provider ) + { + continue; + } + setSchemaLayer( schemaElement, doc, const_cast( vLayer ) ); + } return doc; } + void setSchemaLayer( QDomElement &parentElement, QDomDocument &doc, const QgsVectorLayer *layer ) + { + const QgsVectorDataProvider *provider = layer->dataProvider(); + if ( !provider ) + { + return; + } + + QString typeName = layer->name(); + if ( !layer->shortName().isEmpty() ) + typeName = layer->shortName(); + typeName = typeName.replace( QLatin1String( " " ), QLatin1String( "_" ) ); + + //xsd:element + QDomElement elementElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); + elementElem.setAttribute( QStringLiteral( "name" ), typeName ); + elementElem.setAttribute( QStringLiteral( "type" ), "qgs:" + typeName + "Type" ); + elementElem.setAttribute( QStringLiteral( "substitutionGroup" ), QStringLiteral( "gml:_Feature" ) ); + parentElement.appendChild( elementElem ); + + //xsd:complexType + QDomElement complexTypeElem = doc.createElement( QStringLiteral( "complexType" )/*xsd:complexType*/ ); + complexTypeElem.setAttribute( QStringLiteral( "name" ), typeName + "Type" ); + parentElement.appendChild( complexTypeElem ); + + //xsd:complexType + QDomElement complexContentElem = doc.createElement( QStringLiteral( "complexContent" )/*xsd:complexContent*/ ); + complexTypeElem.appendChild( complexContentElem ); + + //xsd:extension + QDomElement extensionElem = doc.createElement( QStringLiteral( "extension" )/*xsd:extension*/ ); + extensionElem.setAttribute( QStringLiteral( "base" ), QStringLiteral( "gml:AbstractFeatureType" ) ); + complexContentElem.appendChild( extensionElem ); + + //xsd:sequence + QDomElement sequenceElem = doc.createElement( QStringLiteral( "sequence" )/*xsd:sequence*/ ); + extensionElem.appendChild( sequenceElem ); + + //xsd:element + if ( layer->hasGeometryType() ) + { + QDomElement geomElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); + geomElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) ); + if ( provider->name() == QLatin1String( "ogr" ) ) + { + // because some ogr drivers (e.g. ESRI ShapeFile, GML) + // are not able to determine the geometry type of a layer. + // we set to GeometryType + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:GeometryPropertyType" ) ); + } + else + { + QgsWkbTypes::Type wkbType = layer->wkbType(); + switch ( wkbType ) + { + case QgsWkbTypes::Point25D: + case QgsWkbTypes::Point: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:PointPropertyType" ) ); + break; + case QgsWkbTypes::LineString25D: + case QgsWkbTypes::LineString: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:LineStringPropertyType" ) ); + break; + case QgsWkbTypes::Polygon25D: + case QgsWkbTypes::Polygon: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:PolygonPropertyType" ) ); + break; + case QgsWkbTypes::MultiPoint25D: + case QgsWkbTypes::MultiPoint: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiPointPropertyType" ) ); + break; + case QgsWkbTypes::MultiLineString25D: + case QgsWkbTypes::MultiLineString: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiLineStringPropertyType" ) ); + break; + case QgsWkbTypes::MultiPolygon25D: + case QgsWkbTypes::MultiPolygon: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:MultiPolygonPropertyType" ) ); + break; + default: + geomElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "gml:GeometryPropertyType" ) ); + break; + } + } + geomElem.setAttribute( QStringLiteral( "minOccurs" ), QStringLiteral( "0" ) ); + geomElem.setAttribute( QStringLiteral( "maxOccurs" ), QStringLiteral( "1" ) ); + sequenceElem.appendChild( geomElem ); + + //Attributes + const QgsFields &fields = layer->pendingFields(); + //hidden attributes for this layer + const QSet &layerExcludedAttributes = layer->excludeAttributesWfs(); + for ( int idx = 0; idx < fields.count(); ++idx ) + { + + QString attributeName = fields.at( idx ).name(); + //skip attribute if excluded from WFS publication + if ( layerExcludedAttributes.contains( attributeName ) ) + { + continue; + } + + //xsd:element + QDomElement attElem = doc.createElement( QStringLiteral( "element" )/*xsd:element*/ ); + attElem.setAttribute( QStringLiteral( "name" ), attributeName ); + QVariant::Type attributeType = fields.at( idx ).type(); + if ( attributeType == QVariant::Int ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "integer" ) ); + else if ( attributeType == QVariant::LongLong ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "long" ) ); + else if ( attributeType == QVariant::Double ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "double" ) ); + else if ( attributeType == QVariant::Bool ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "boolean" ) ); + else if ( attributeType == QVariant::Date ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "date" ) ); + else if ( attributeType == QVariant::Time ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "time" ) ); + else if ( attributeType == QVariant::DateTime ) + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "dateTime" ) ); + else + attElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "string" ) ); + + sequenceElem.appendChild( attElem ); + + QString alias = fields.at( idx ).alias(); + if ( !alias.isEmpty() ) + { + attElem.setAttribute( QStringLiteral( "alias" ), alias ); + } + } + } + } + } // samespace QgsWfs diff --git a/src/server/services/wfs/qgswfsdescribefeaturetype.h b/src/server/services/wfs/qgswfsdescribefeaturetype.h index 535147a487b..4bb87131c9b 100644 --- a/src/server/services/wfs/qgswfsdescribefeaturetype.h +++ b/src/server/services/wfs/qgswfsdescribefeaturetype.h @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -21,20 +22,24 @@ #ifndef QGSWFSDESCRIBEFEATURETYPE_H #define QGSWFSDESCRIBEFEATURETYPE_H +#include "qgsvectorlayer.h" + #include +#include namespace QgsWfs { + void setSchemaLayer( QDomElement &parentElement, QDomDocument &doc, const QgsVectorLayer *layer ); /** * Create get capabilities document */ - QDomDocument createDescribeFeatureTypeDocument( QgsServerInterface *serverIface, const QString &version, + QDomDocument createDescribeFeatureTypeDocument( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request ); /** Output WFS GetCapabilities response */ - void writeDescribeFeatureType( QgsServerInterface *serverIface, const QString &version, + void writeDescribeFeatureType( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ); } // samespace QgsWfs diff --git a/src/server/services/wfs/qgswfsgetcapabilities.cpp b/src/server/services/wfs/qgswfsgetcapabilities.cpp index e778b181a1e..b4296cd8a46 100644 --- a/src/server/services/wfs/qgswfsgetcapabilities.cpp +++ b/src/server/services/wfs/qgswfsgetcapabilities.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -19,8 +20,18 @@ * * ***************************************************************************/ #include "qgswfsutils.h" +#include "qgsserverprojectutils.h" #include "qgswfsgetcapabilities.h" +#include "qgsproject.h" +#include "qgscsexception.h" +#include "qgsvectorlayer.h" +#include "qgsvectordataprovider.h" +#include "qgsmapserviceexception.h" +#include "qgscoordinatereferencesystem.h" + +#include + namespace QgsWfs { @@ -44,8 +55,6 @@ namespace QgsWfs QDomDocument doc; - QgsWfsProjectParser *configParser = getConfigParser( serverIface ); - //wfs:WFS_Capabilities element QDomElement wfsCapabilitiesElement = doc.createElement( QStringLiteral( "WFS_Capabilities" )/*wms:WFS_Capabilities*/ ); wfsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns" ), WFS_NAMESPACE ); @@ -59,7 +68,9 @@ namespace QgsWfs wfsCapabilitiesElement.setAttribute( QStringLiteral( "updateSequence" ), QStringLiteral( "0" ) ); doc.appendChild( wfsCapabilitiesElement ); - configParser->serviceCapabilities( wfsCapabilitiesElement, doc ); + //configParser->serviceCapabilities( wfsCapabilitiesElement, doc ); + //INSERT Service + wfsCapabilitiesElement.appendChild( getServiceElement( doc, project ) ); //wfs:Capability element QDomElement capabilityElement = doc.createElement( QStringLiteral( "Capability" )/*wfs:Capability*/ ); @@ -125,19 +136,8 @@ namespace QgsWfs transactionDhcTypeElement.firstChild().firstChild().toElement().setTagName( QStringLiteral( "Post" ) ); transactionElement.appendChild( transactionDhcTypeElement ); - //wfs:FeatureTypeList element - QDomElement featureTypeListElement = doc.createElement( QStringLiteral( "FeatureTypeList" )/*wfs:FeatureTypeList*/ ); - wfsCapabilitiesElement.appendChild( featureTypeListElement ); - //wfs:Operations element - QDomElement operationsElement = doc.createElement( QStringLiteral( "Operations" )/*wfs:Operations*/ ); - featureTypeListElement.appendChild( operationsElement ); - //wfs:Query element - QDomElement queryElement = doc.createElement( QStringLiteral( "Query" )/*wfs:Query*/ ); - operationsElement.appendChild( queryElement ); - /* - * Adding layer liste in featureTypeListElement - */ - configParser->featureTypeList( featureTypeListElement, doc ); + //wfs:FeatureTypeList + wfsCapabilitiesElement.appendChild( getFeatureTypeListElement( doc, serverIface, project ) ); /* * Adding ogc:Filter_Capabilities in capabilityElement @@ -169,6 +169,224 @@ namespace QgsWfs } + QDomElement getServiceElement( QDomDocument &doc, const QgsProject *project ) + { + //Service element + QDomElement serviceElem = doc.createElement( QStringLiteral( "Service" ) ); + + //Service name + QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) ); + QDomText nameText = doc.createTextNode( "WFS" ); + nameElem.appendChild( nameText ); + serviceElem.appendChild( nameElem ); + + QString title = QgsServerProjectUtils::owsServiceTitle( *project ); + if ( !title.isEmpty() ) + { + QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) ); + QDomText titleText = doc.createTextNode( title ); + titleElem.appendChild( titleText ); + serviceElem.appendChild( titleElem ); + } + + QString abstract = QgsServerProjectUtils::owsServiceAbstract( *project ); + if ( !abstract.isEmpty() ) + { + QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) ); + QDomText abstractText = doc.createCDATASection( abstract ); + abstractElem.appendChild( abstractText ); + serviceElem.appendChild( abstractElem ); + } + + QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project ); + if ( !keywords.isEmpty() && !keywords.join( QStringLiteral( ", " ) ).isEmpty() ) + { + QDomElement keywordsElem = doc.createElement( QStringLiteral( "Keywords" ) ); + QDomText keywordsText = doc.createTextNode( keywords.join( QStringLiteral( ", " ) ) ); + keywordsElem.appendChild( keywordsText ); + serviceElem.appendChild( keywordsElem ); + } + + QDomElement onlineResourceElem = doc.createElement( QStringLiteral( "OnlineResource" ) ); + QString onlineResource = QgsServerProjectUtils::owsServiceOnlineResource( *project ); + if ( !onlineResource.isEmpty() ) + { + QDomText onlineResourceText = doc.createTextNode( onlineResource ); + onlineResourceElem.appendChild( onlineResourceText ); + } + serviceElem.appendChild( onlineResourceElem ); + + QString fees = QgsServerProjectUtils::owsServiceFees( *project ); + if ( !fees.isEmpty() ) + { + QDomElement feesElem = doc.createElement( QStringLiteral( "Fees" ) ); + QDomText feesText = doc.createTextNode( fees ); + feesElem.appendChild( feesText ); + serviceElem.appendChild( feesElem ); + } + + QString accessConstraints = QgsServerProjectUtils::owsServiceAccessConstraints( *project ); + if ( !accessConstraints.isEmpty() ) + { + QDomElement accessConstraintsElem = doc.createElement( QStringLiteral( "AccessConstraints" ) ); + QDomText accessConstraintsText = doc.createTextNode( accessConstraints ); + accessConstraintsElem.appendChild( accessConstraintsText ); + serviceElem.appendChild( accessConstraintsElem ); + } + + return serviceElem; + + } + + QDomElement getFeatureTypeListElement( QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project ) + { + QgsAccessControl *accessControl = serverIface->accessControls(); + + //wfs:FeatureTypeList element + QDomElement featureTypeListElement = doc.createElement( QStringLiteral( "FeatureTypeList" )/*wfs:FeatureTypeList*/ ); + //wfs:Operations element + QDomElement operationsElement = doc.createElement( QStringLiteral( "Operations" )/*wfs:Operations*/ ); + featureTypeListElement.appendChild( operationsElement ); + //wfs:Query element + QDomElement queryElement = doc.createElement( QStringLiteral( "Query" )/*wfs:Query*/ ); + operationsElement.appendChild( queryElement ); + + QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + QStringList wfstUpdateLayersId = QgsServerProjectUtils::wfstUpdateLayerIds( *project ); + QStringList wfstInsertLayersId = QgsServerProjectUtils::wfstInsertLayerIds( *project ); + QStringList wfstDeleteLayersId = QgsServerProjectUtils::wfstDeleteLayerIds( *project ); + for ( int i = 0; i < wfsLayerIds.size(); ++i ) + { + QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) ); + if ( layer->type() != QgsMapLayer::LayerType::VectorLayer ) + { + continue; + } + if ( accessControl && !accessControl->layerReadPermission( layer ) ) + { + continue; + } + + QDomElement layerElem = doc.createElement( QStringLiteral( "FeatureType" ) ); + + //create Name + QDomElement nameElem = doc.createElement( QStringLiteral( "Name" ) ); + QString typeName = layer->name(); + if ( !layer->shortName().isEmpty() ) + typeName = layer->shortName(); + typeName = typeName.replace( QLatin1String( " " ), QLatin1String( "_" ) ); + QDomText nameText = doc.createTextNode( typeName ); + nameElem.appendChild( nameText ); + layerElem.appendChild( nameElem ); + + //create Title + QDomElement titleElem = doc.createElement( QStringLiteral( "Title" ) ); + QString title = layer->title(); + if ( title.isEmpty() ) + { + title = layer->name(); + } + QDomText titleText = doc.createTextNode( title ); + titleElem.appendChild( titleText ); + layerElem.appendChild( titleElem ); + + //create Abstract + QString abstract = layer->abstract(); + if ( !abstract.isEmpty() ) + { + QDomElement abstractElem = doc.createElement( QStringLiteral( "Abstract" ) ); + QDomText abstractText = doc.createTextNode( abstract ); + abstractElem.appendChild( abstractText ); + layerElem.appendChild( abstractElem ); + } + + //create keywords + QString keywords = layer->keywordList(); + if ( !keywords.isEmpty() ) + { + QDomElement keywordsElem = doc.createElement( QStringLiteral( "Keywords" ) ); + QDomText keywordsText = doc.createTextNode( keywords ); + keywordsElem.appendChild( keywordsText ); + layerElem.appendChild( keywordsElem ); + } + + //create SRS + QDomElement srsElem = doc.createElement( QStringLiteral( "SRS" ) ); + QDomText srsText = doc.createTextNode( layer->crs().authid() ); + srsElem.appendChild( srsText ); + layerElem.appendChild( srsElem ); + + //create LatLongBoundingBox + QgsRectangle layerExtent = layer->extent(); + QDomElement bBoxElement = doc.createElement( QStringLiteral( "LatLongBoundingBox" ) ); + bBoxElement.setAttribute( QStringLiteral( "minx" ), QString::number( layerExtent.xMinimum() ) ); + bBoxElement.setAttribute( QStringLiteral( "miny" ), QString::number( layerExtent.yMinimum() ) ); + bBoxElement.setAttribute( QStringLiteral( "maxx" ), QString::number( layerExtent.xMaximum() ) ); + bBoxElement.setAttribute( QStringLiteral( "maxy" ), QString::number( layerExtent.yMaximum() ) ); + layerElem.appendChild( bBoxElement ); + + // layer metadata URL + QString metadataUrl = layer->metadataUrl(); + if ( !metadataUrl.isEmpty() ) + { + QDomElement metaUrlElem = doc.createElement( QStringLiteral( "MetadataURL" ) ); + QString metadataUrlType = layer->metadataUrlType(); + metaUrlElem.setAttribute( QStringLiteral( "type" ), metadataUrlType ); + QString metadataUrlFormat = layer->metadataUrlFormat(); + if ( metadataUrlFormat == QLatin1String( "text/xml" ) ) + { + metaUrlElem.setAttribute( QStringLiteral( "format" ), QStringLiteral( "XML" ) ); + } + else + { + metaUrlElem.setAttribute( QStringLiteral( "format" ), QStringLiteral( "TXT" ) ); + } + QDomText metaUrlText = doc.createTextNode( metadataUrl ); + metaUrlElem.appendChild( metaUrlText ); + layerElem.appendChild( metaUrlElem ); + } + + //wfs:Operations element + QDomElement operationsElement = doc.createElement( QStringLiteral( "Operations" )/*wfs:Operations*/ ); + //wfs:Query element + QDomElement queryElement = doc.createElement( QStringLiteral( "Query" )/*wfs:Query*/ ); + operationsElement.appendChild( queryElement ); + if ( wfstUpdateLayersId.contains( layer->id() ) || + wfstInsertLayersId.contains( layer->id() ) || + wfstDeleteLayersId.contains( layer->id() ) ) + { + QgsVectorLayer *vlayer = qobject_cast( layer ); + QgsVectorDataProvider *provider = vlayer->dataProvider(); + if ( ( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) && wfstInsertLayersId.contains( layer->id() ) ) + { + //wfs:Insert element + QDomElement insertElement = doc.createElement( QStringLiteral( "Insert" )/*wfs:Insert*/ ); + operationsElement.appendChild( insertElement ); + } + if ( ( provider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) && + ( provider->capabilities() & QgsVectorDataProvider::ChangeGeometries ) && + wfstUpdateLayersId.contains( layer->id() ) ) + { + //wfs:Update element + QDomElement updateElement = doc.createElement( QStringLiteral( "Update" )/*wfs:Update*/ ); + operationsElement.appendChild( updateElement ); + } + if ( ( provider->capabilities() & QgsVectorDataProvider::DeleteFeatures ) && wfstDeleteLayersId.contains( layer->id() ) ) + { + //wfs:Delete element + QDomElement deleteElement = doc.createElement( QStringLiteral( "Delete" )/*wfs:Delete*/ ); + operationsElement.appendChild( deleteElement ); + } + } + + layerElem.appendChild( operationsElement ); + + featureTypeListElement.appendChild( layerElem ); + } + + return featureTypeListElement; + } + } // samespace QgsWfs diff --git a/src/server/services/wfs/qgswfsgetcapabilities.h b/src/server/services/wfs/qgswfsgetcapabilities.h index 877b574322d..5651c42fbaf 100644 --- a/src/server/services/wfs/qgswfsgetcapabilities.h +++ b/src/server/services/wfs/qgswfsgetcapabilities.h @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -26,6 +27,16 @@ namespace QgsWfs { + /** + * Create FeatureTypeList element for get capabilities document + */ + QDomElement getFeatureTypeListElement( QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project ); + + /** + * Create Service element for get capabilities document + */ + QDomElement getServiceElement( QDomDocument &doc, const QgsProject *project ); + /** * Create get capabilities document */ diff --git a/src/server/services/wfs/qgswfsgetfeature.cpp b/src/server/services/wfs/qgswfsgetfeature.cpp index 378f7830b18..017bac18928 100644 --- a/src/server/services/wfs/qgswfsgetfeature.cpp +++ b/src/server/services/wfs/qgswfsgetfeature.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -19,6 +20,7 @@ * * ***************************************************************************/ #include "qgswfsutils.h" +#include "qgsserverprojectutils.h" #include "qgsfields.h" #include "qgsexpression.h" #include "qgsgeometry.h" @@ -71,837 +73,744 @@ namespace QgsWfs { Q_UNUSED( version ); - QgsWfsProjectParser *configParser = getConfigParser( serverIface ); - QgsServerRequest::Parameters parameters = request.parameters(); - QgsAccessControl *accessControl = serverIface->accessControls(); - - QStringList wfsLayersId = configParser->wfsLayers(); - - QList layerList; - QgsMapLayer *currentLayer = nullptr; - QgsCoordinateReferenceSystem layerCrs; - QgsRectangle searchRect( 0, 0, 0, 0 ); - - QStringList errors; - QStringList typeNames; - QString typeName; - QString propertyName; - QString geometryName; - - QString format = parameters.value( QStringLiteral( "OUTPUTFORMAT" ) ); - - bool withGeom = true; - - long maxFeatures = 0; - bool hasFeatureLimit = false; - long startIndex = 0; - long featureCounter = 0; - int layerPrec = 8; - long featCounter = 0; - - QgsExpressionContext expressionContext; - expressionContext << QgsExpressionContextUtils::globalScope() - << QgsExpressionContextUtils::projectScope( QgsProject::instance() ); + getFeatureRequest aRequest; QDomDocument doc; QString errorMsg; + if ( doc.setContent( parameters.value( QStringLiteral( "REQUEST_BODY" ) ), true, &errorMsg ) ) + { + QDomElement docElem = doc.documentElement(); + aRequest = parseGetFeatureRequestBody( docElem ); + } + else + { + aRequest = parseGetFeatureParameters( parameters ); + } + + // store typeName + QStringList typeNameList; + + // Request metadata + bool onlyOneLayer = ( aRequest.queries.size() == 1 ); + QgsRectangle requestRect; + QgsCoordinateReferenceSystem requestCrs; + int requestPrecision = 6; + if ( !onlyOneLayer ) + requestCrs = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem::EpsgCrsId ); + + QList::iterator qIt = aRequest.queries.begin(); + for ( ; qIt != aRequest.queries.end(); ++qIt ) + { + typeNameList << ( *qIt ).typeName; + } + + // get layers and + // update the request metadata + QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + QMap mapLayerMap; + for ( int i = 0; i < wfsLayerIds.size(); ++i ) + { + QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) ); + if ( layer->type() != QgsMapLayer::LayerType::VectorLayer ) + { + continue; + } + + QString name = layer->name(); + if ( !layer->shortName().isEmpty() ) + name = layer->shortName(); + name = name.replace( QLatin1String( " " ), QLatin1String( "_" ) ); + + if ( typeNameList.contains( name ) ) + { + // store layers + mapLayerMap[name] = layer; + // update request metadata + if ( onlyOneLayer ) + { + requestRect = layer->extent(); + requestCrs = layer->crs(); + } + else + { + QgsCoordinateTransform transform( layer->crs(), requestCrs ); + try + { + if ( requestRect.isEmpty() ) + { + requestRect = transform.transform( layer->extent() ); + } + else + { + requestRect.combineExtentWith( transform.transform( layer->extent() ) ); + } + } + catch ( QgsException &cse ) + { + Q_UNUSED( cse ); + requestRect = QgsRectangle( -180.0, -90.0, 180.0, 90.0 ); + } + } + } + } + + QgsAccessControl *accessControl = serverIface->accessControls(); + //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer( accessControl ) ); - if ( doc.setContent( parameters.value( QStringLiteral( "REQUEST_BODY" ) ), true, &errorMsg ) ) + // features counters + long sentFeatures = 0; + long iteratedFeatures = 0; + // sent features + QgsFeature feature; + qIt = aRequest.queries.begin(); + for ( ; qIt != aRequest.queries.end(); ++qIt ) { - QDomElement docElem = doc.documentElement(); - if ( docElem.hasAttribute( QStringLiteral( "maxFeatures" ) ) ) + getFeatureQuery &query = *qIt; + QString typeName = query.typeName; + + if ( !mapLayerMap.keys().contains( typeName ) ) { - hasFeatureLimit = true; - maxFeatures = docElem.attribute( QStringLiteral( "maxFeatures" ) ).toLong(); - } - if ( docElem.hasAttribute( QStringLiteral( "startIndex" ) ) ) - { - startIndex = docElem.attribute( QStringLiteral( "startIndex" ) ).toLong(); + throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' unknown" ).arg( typeName ) ); } - QDomNodeList queryNodes = docElem.elementsByTagName( QStringLiteral( "Query" ) ); - QDomElement queryElem; - for ( int i = 0; i < queryNodes.size(); i++ ) + QgsMapLayer *layer = mapLayerMap[typeName]; + if ( accessControl && !accessControl->layerReadPermission( layer ) ) { - queryElem = queryNodes.at( 0 ).toElement(); - typeName = queryElem.attribute( QStringLiteral( "typeName" ), QLatin1String( "" ) ); - if ( typeName.contains( QLatin1String( ":" ) ) ) - { - typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); - } - typeNames << typeName; - } - for ( int i = 0; i < queryNodes.size(); i++ ) - { - queryElem = queryNodes.at( 0 ).toElement(); - typeName = queryElem.attribute( QStringLiteral( "typeName" ), QLatin1String( "" ) ); - if ( typeName.contains( QLatin1String( ":" ) ) ) - { - typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); - } - - layerList = configParser->mapLayerFromTypeName( typeName ); - if ( layerList.size() < 1 ) - { - errors << QStringLiteral( "The layer for the TypeName '%1' is not found" ).arg( typeName ); - continue; - } - - currentLayer = layerList.at( 0 ); - QgsVectorLayer *layer = qobject_cast( currentLayer ); - if ( layer && wfsLayersId.contains( layer->id() ) ) - { -#ifdef HAVE_SERVER_PYTHON_PLUGINS - if ( !accessControl->layerReadPermission( currentLayer ) ) - { - throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) ); - } - QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, currentLayer, filterRestorer->originalFilters() ); -#endif - expressionContext << QgsExpressionContextUtils::layerScope( layer ); - - //is there alias info for this vector layer? - QMap< int, QString > layerAliasInfo; - QgsStringMap aliasMap = layer->attributeAliases(); - QgsStringMap::const_iterator aliasIt = aliasMap.constBegin(); - for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt ) - { - int attrIndex = layer->fields().lookupField( aliasIt.key() ); - if ( attrIndex != -1 ) - { - layerAliasInfo.insert( attrIndex, aliasIt.value() ); - } - } - - //excluded attributes for this layer - const QSet &layerExcludedAttributes = layer->excludeAttributesWfs(); - - //get layer precision - layerPrec = configParser->wfsLayerPrecision( layer->id() ); - - //do a select with searchRect and go through all the features - QgsVectorDataProvider *provider = layer->dataProvider(); - if ( !provider ) - { - errors << QStringLiteral( "The layer's provider for the TypeName '%1' is not found" ).arg( typeName ); - continue; - } - - QgsFeature feature; - - withGeom = true; - - //Using pending attributes and pending fields - QgsAttributeList attrIndexes = layer->pendingAllAttributesList(); - - QDomNodeList queryChildNodes = queryElem.childNodes(); - if ( queryChildNodes.size() ) - { - QStringList::const_iterator alstIt; - QList idxList; - QgsFields fields = layer->pendingFields(); - QString fieldName; - QDomElement propertyElem; - for ( int q = 0; q < queryChildNodes.size(); q++ ) - { - QDomElement queryChildElem = queryChildNodes.at( q ).toElement(); - if ( queryChildElem.tagName() == QLatin1String( "PropertyName" ) ) - { - fieldName = queryChildElem.text(); - if ( fieldName.contains( QLatin1String( ":" ) ) ) - { - fieldName = fieldName.section( QStringLiteral( ":" ), 1, 1 ); - } - int fieldNameIdx = fields.lookupField( fieldName ); - if ( fieldNameIdx > -1 ) - { - idxList.append( fieldNameIdx ); - } - } - } - if ( !idxList.isEmpty() ) - { - attrIndexes = idxList; - } - } - - //map extent - searchRect = layer->extent(); - searchRect.set( searchRect.xMinimum() - 1. / pow( 10., layerPrec ) - , searchRect.yMinimum() - 1. / pow( 10., layerPrec ) - , searchRect.xMaximum() + 1. / pow( 10., layerPrec ) - , searchRect.yMaximum() + 1. / pow( 10., layerPrec ) ); - layerCrs = layer->crs(); - - QgsFeatureRequest fReq; -#ifdef HAVE_SERVER_PYTHON_PLUGINS - fReq.setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - accessControl->filterFeatures( layer, fReq ); - - QStringList attributes = QStringList(); - Q_FOREACH ( int idx, attrIndexes ) - { - attributes.append( layer->pendingFields().field( idx ).name() ); - } - fReq.setSubsetOfAttributes( - accessControl->layerAttributes( layer, attributes ), - layer->pendingFields() ); -#endif - - QgsFeatureIterator fit = layer->getFeatures( fReq ); - - QDomNodeList filterNodes = queryElem.elementsByTagName( QStringLiteral( "Filter" ) ); - if ( !filterNodes.isEmpty() ) - { - QDomElement filterElem = filterNodes.at( 0 ).toElement(); - QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) ); - if ( !fidNodes.isEmpty() ) - { - QDomElement fidElem; - QString fid = QLatin1String( "" ); - for ( int f = 0; f < fidNodes.size(); f++ ) - { - fidElem = fidNodes.at( f ).toElement(); - fid = fidElem.attribute( QStringLiteral( "fid" ) ); - if ( fid.contains( QLatin1String( "." ) ) ) - { - if ( fid.section( QStringLiteral( "." ), 0, 0 ) != typeName ) - continue; - fid = fid.section( QStringLiteral( "." ), 1, 1 ); - } - - //Need to be test for propertyname - layer->getFeatures( QgsFeatureRequest() - .setFilterFid( fid.toInt() ) - .setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ) - .setSubsetOfAttributes( attrIndexes ) - ).nextFeature( feature ); - - if ( featureCounter == 0 ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - - fid = QLatin1String( "" ); - ++featCounter; - ++featureCounter; - } - } - else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) ) - { - QDomElement bboxElem = filterElem.firstChildElement(); - QDomElement childElem = bboxElem.firstChildElement(); - - QgsFeatureRequest req; - req.setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - - while ( !childElem.isNull() ) - { - if ( childElem.tagName() == QLatin1String( "Box" ) ) - { - req.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) ); - } - else if ( childElem.tagName() != QLatin1String( "PropertyName" ) ) - { - QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem ); - req.setFilterRect( geom.boundingBox() ); - } - childElem = childElem.nextSiblingElement(); - } - req.setSubsetOfAttributes( attrIndexes ); - - QgsFeatureIterator fit = layer->getFeatures( req ); - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - else - { - std::shared_ptr filter( QgsOgcUtils::expressionFromOgcFilter( filterElem ) ); - if ( filter ) - { - if ( filter->hasParserError() ) - { - throw QgsRequestNotWellFormedException( filter->parserErrorString() ); - } - QgsFeatureRequest req; - if ( filter->needsGeometry() ) - { - req.setFlags( QgsFeatureRequest::NoFlags ); - } - else - { - req.setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - } - req.setFilterExpression( filter->expression() ); -#ifdef HAVE_SERVER_PYTHON_PLUGINS - accessControl->filterFeatures( layer, req ); - - QStringList attributes = QStringList(); - Q_FOREACH ( int idx, attrIndexes ) - { - attributes.append( layer->pendingFields().field( idx ).name() ); - } - req.setSubsetOfAttributes( - accessControl->layerAttributes( layer, attributes ), - layer->pendingFields() ); -#endif - QgsFeatureIterator fit = layer->getFeatures( req ); - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - expressionContext.setFeature( feature ); - - QVariant res = filter->evaluate( &expressionContext ); - if ( filter->hasEvalError() ) - { - // XXX It is useless to throw an error at this point if startGetFeature has been called - // because request content has already been flushed to client - throw QgsRequestNotWellFormedException( filter->evalErrorString() ); - } - if ( res.toInt() != 0 ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - } - } - } - else - { - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - } - else - { - errors << QStringLiteral( "The layer for the TypeName '%1' is not a WFS layer" ).arg( typeName ); - } - + throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) ); } - //force restoration of original layer filters - filterRestorer.reset(); + QgsVectorLayer *vlayer = qobject_cast( layer ); + if ( !vlayer ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer error" ).arg( typeName ) ); + } - QgsMessageLog::logMessage( errors.join( QStringLiteral( "\n" ) ) ); + //test provider + QgsVectorDataProvider *provider = vlayer->dataProvider(); + if ( !provider ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer's provider error" ).arg( typeName ) ); + } - QgsProject::instance()->removeAllMapLayers(); - if ( featureCounter <= startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - endGetFeature( response, format ); - return; + if ( accessControl ) + { + QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, vlayer, filterRestorer->originalFilters() ); + } + + //is there alias info for this vector layer? + QMap< int, QString > layerAliasInfo; + QgsStringMap aliasMap = vlayer->attributeAliases(); + QgsStringMap::const_iterator aliasIt = aliasMap.constBegin(); + for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt ) + { + int attrIndex = vlayer->fields().lookupField( aliasIt.key() ); + if ( attrIndex != -1 ) + { + layerAliasInfo.insert( attrIndex, aliasIt.value() ); + } + } + + // get propertyList from query + QStringList propertyList = query.propertyList; + + //Using pending attributes and pending fields + QgsAttributeList attrIndexes = vlayer->pendingAllAttributesList(); + bool withGeom = true; + if ( !propertyList.isEmpty() && propertyList.first() != QStringLiteral( "*" ) ) + { + withGeom = false; + QStringList::const_iterator plstIt; + QList idxList; + QgsFields fields = vlayer->pendingFields(); + QString fieldName; + for ( plstIt = propertyList.begin(); plstIt != propertyList.end(); ++plstIt ) + { + fieldName = *plstIt; + int fieldNameIdx = fields.lookupField( fieldName ); + if ( fieldNameIdx > -1 ) + { + idxList.append( fieldNameIdx ); + } + else if ( fieldName == QStringLiteral( "geometry" ) ) + { + withGeom = true; + } + } + if ( !idxList.isEmpty() ) + { + attrIndexes = idxList; + } + } + + + // update request + QgsFeatureRequest featureRequest = query.featureRequest; + + // expression context + QgsExpressionContext expressionContext; + expressionContext << QgsExpressionContextUtils::globalScope() + << QgsExpressionContextUtils::projectScope( project ) + << QgsExpressionContextUtils::layerScope( vlayer ); + featureRequest.setExpressionContext( expressionContext ); + + // geometry flags + if ( vlayer->wkbType() == QgsWkbTypes::NoGeometry ) + featureRequest.setFlags( featureRequest.flags() | QgsFeatureRequest::NoGeometry ); + else + featureRequest.setFlags( featureRequest.flags() | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); + // subset of attributes + featureRequest.setSubsetOfAttributes( attrIndexes ); + + if ( accessControl ) + { + accessControl->filterFeatures( vlayer, featureRequest ); + + QStringList attributes = QStringList(); + Q_FOREACH ( int idx, attrIndexes ) + { + attributes.append( vlayer->pendingFields().field( idx ).name() ); + } + featureRequest.setSubsetOfAttributes( + accessControl->layerAttributes( vlayer, attributes ), + vlayer->pendingFields() ); + } + + if ( onlyOneLayer ) + { + requestPrecision = QgsServerProjectUtils::wfsLayerPrecision( vlayer->id(), *project ); + } + + if ( onlyOneLayer && !featureRequest.filterRect().isEmpty() ) + { + requestRect = featureRequest.filterRect(); + } + + if ( aRequest.maxFeatures > 0 ) + { + featureRequest.setLimit( aRequest.maxFeatures + aRequest.startIndex - sentFeatures ); + } + // specific layer precision + int layerPrecision = QgsServerProjectUtils::wfsLayerPrecision( vlayer->id(), *project ); + // specific layer crs + QgsCoordinateReferenceSystem layerCrs = vlayer->crs(); + //excluded attributes for this layer + const QSet &layerExcludedAttributes = vlayer->excludeAttributesWfs(); + // Geometry name + QString geometryName = aRequest.geometryName; + if ( !withGeom ) + { + geometryName = QLatin1String( "NONE" ); + } + + // Iterate through features + QgsFeatureIterator fit = vlayer->getFeatures( featureRequest ); + while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) ) + { + if ( iteratedFeatures == aRequest.startIndex ) + startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList ); + + if ( iteratedFeatures >= aRequest.startIndex ) + { + setGetFeature( response, aRequest.outputFormat, &feature, sentFeatures, layerPrecision, layerCrs, attrIndexes, layerExcludedAttributes, + typeName, withGeom, geometryName ); + ++sentFeatures; + } + ++iteratedFeatures; + } } - // Information about parameters - // FILTER - bool filterOk = false; - QDomDocument filter; - // EXP_FILTER - bool expFilterOk = false; - QString expFilter; - // BBOX - bool bboxOk = false; - double minx = 0.0, miny = 0.0, maxx = 0.0, maxy = 0.0; +#ifdef HAVE_SERVER_PYTHON_PLUGINS + //force restoration of original layer filters + filterRestorer.reset(); +#endif - //read FEATUREDID - bool featureIdOk = false; - QStringList featureIdList; - QMap::const_iterator feature_id_it = parameters.constFind( QStringLiteral( "FEATUREID" ) ); - if ( feature_id_it != parameters.constEnd() ) + // End of GetFeature + if ( iteratedFeatures <= aRequest.startIndex ) + startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList ); + endGetFeature( response, aRequest.outputFormat ); + + } + + getFeatureRequest parseGetFeatureParameters( QgsServerRequest::Parameters parameters ) + { + getFeatureRequest request; + request.maxFeatures = -1; + request.startIndex = 0; + request.outputFormat = parameters.value( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "GML2" ) ); + + // Verifying parameters mutually exclusive + if ( ( parameters.contains( QStringLiteral( "FEATUREID" ) ) + && ( parameters.contains( QStringLiteral( "FILTER" ) ) || parameters.contains( QStringLiteral( "BBOX" ) ) ) ) + || ( parameters.contains( QStringLiteral( "FILTER" ) ) + && ( parameters.contains( QStringLiteral( "FEATUREID" ) ) || parameters.contains( QStringLiteral( "BBOX" ) ) ) ) + || ( parameters.contains( QStringLiteral( "BBOX" ) ) + && ( parameters.contains( QStringLiteral( "FEATUREID" ) ) || parameters.contains( QStringLiteral( "FILTER" ) ) ) ) + ) { - featureIdOk = true; - featureIdList = feature_id_it.value().split( QStringLiteral( "," ) ); - QStringList typeNameList; - Q_FOREACH ( const QString &fidStr, featureIdList ) - { - // testing typename in the WFS featureID - if ( !fidStr.contains( QLatin1String( "." ) ) ) - throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) ); - - QString typeName = fidStr.section( QStringLiteral( "." ), 0, 0 ); - if ( !typeNameList.contains( typeName ) ) - typeNameList << typeName; - } - - typeName = typeNameList.join( QStringLiteral( "," ) ); + throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) ); } - if ( !featureIdOk ) + // Get and split PROPERTYNAME parameter + QStringList propertyNameList; + if ( parameters.contains( QStringLiteral( "PROPERTYNAME" ) ) ) { - //read TYPENAME - QMap::const_iterator type_name_it = parameters.constFind( QStringLiteral( "TYPENAME" ) ); - if ( type_name_it != parameters.constEnd() ) + QString propertyName = parameters.value( QStringLiteral( "PROPERTYNAME" ) ); + QRegExp rx( "\\(([^()]+)\\)" ); + if ( rx.indexIn( propertyName, 0 ) == -1 ) { - typeName = type_name_it.value(); + propertyNameList << propertyName; } else { - throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is MANDATORY" ) ); - } - - //read FILTER - QMap::const_iterator filterIt = parameters.constFind( QStringLiteral( "FILTER" ) ); - if ( filterIt != parameters.constEnd() ) - { - QString errorMsg; - if ( !filter.setContent( filterIt.value(), true, &errorMsg ) ) + int pos = 0; + while ( ( pos = rx.indexIn( propertyName, pos ) ) != -1 ) { - throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, filterIt.value() ) ); - } - else - { - filterOk = true; - } - } - - //read EXP_FILTER - if ( !filterOk ) - { - QMap::const_iterator expFilterIt = parameters.constFind( QStringLiteral( "EXP_FILTER" ) ); - if ( expFilterIt != parameters.constEnd() ) - { - expFilterOk = true; - expFilter = expFilterIt.value(); - } - } - - //read BBOX - if ( !filterOk ) - { - QMap::const_iterator bbIt = parameters.constFind( QStringLiteral( "BBOX" ) ); - if ( bbIt == parameters.constEnd() ) - { - minx = 0; - miny = 0; - maxx = 0; - maxy = 0; - } - else - { - bool conversionSuccess; - bboxOk = true; - QString bbString = bbIt.value(); - minx = bbString.section( QStringLiteral( "," ), 0, 0 ).toDouble( &conversionSuccess ); - bboxOk &= conversionSuccess; - miny = bbString.section( QStringLiteral( "," ), 1, 1 ).toDouble( &conversionSuccess ); - bboxOk &= conversionSuccess; - maxx = bbString.section( QStringLiteral( "," ), 2, 2 ).toDouble( &conversionSuccess ); - bboxOk &= conversionSuccess; - maxy = bbString.section( QStringLiteral( "," ), 3, 3 ).toDouble( &conversionSuccess ); - bboxOk &= conversionSuccess; + propertyNameList << rx.cap( 1 ); + pos += rx.matchedLength(); } } } - //read MAXFEATURES - QMap::const_iterator mfIt = parameters.constFind( QStringLiteral( "MAXFEATURES" ) ); - if ( mfIt != parameters.constEnd() ) + // Manage extra parameter GeometryName + request.geometryName = QLatin1String( "" ); + if ( parameters.contains( QStringLiteral( "GEOMETRYNAME" ) ) ) { - QString mfString = mfIt.value(); - bool mfOk; - hasFeatureLimit = true; - maxFeatures = mfString.toLong( &mfOk, 10 ); + request.geometryName = parameters.value( QStringLiteral( "GEOMETRYNAME" ) ).toUpper(); } - //read STARTINDEX - QMap::const_iterator siIt = parameters.constFind( QStringLiteral( "STARTINDEX" ) ); - if ( siIt != parameters.constEnd() ) + QStringList typeNameList; + // parse FEATUREID + if ( parameters.contains( QStringLiteral( "FEATUREID" ) ) ) { - QString siString = siIt.value(); - bool siOk; - startIndex = siString.toLong( &siOk, 10 ); - } - - //read PROPERTYNAME - withGeom = true; - propertyName = QStringLiteral( "*" ); - QMap::const_iterator pnIt = parameters.constFind( QStringLiteral( "PROPERTYNAME" ) ); - if ( pnIt != parameters.constEnd() ) - { - propertyName = pnIt.value(); - } - geometryName = QLatin1String( "" ); - QMap::const_iterator gnIt = parameters.constFind( QStringLiteral( "GEOMETRYNAME" ) ); - if ( gnIt != parameters.constEnd() ) - { - geometryName = gnIt.value().toUpper(); - } - - typeNames = typeName.split( QStringLiteral( "," ) ); - Q_FOREACH ( const QString &tnStr, typeNames ) - { - typeName = tnStr; - layerList = configParser->mapLayerFromTypeName( tnStr ); - if ( layerList.size() < 1 ) + QStringList fidList = parameters.value( QStringLiteral( "FEATUREID" ) ).split( QStringLiteral( "," ) ); + // Verifying the 1:1 mapping between FEATUREID and PROPERTYNAME + if ( !propertyNameList.isEmpty() && propertyNameList.size() != fidList.size() ) { - errors << QStringLiteral( "The layer for the TypeName '%1' is not found" ).arg( tnStr ); - continue; + throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a FEATUREID and the PROPERTYNAME list" ) ); + } + if ( propertyNameList.isEmpty() ) + { + for ( int i = 0; i < fidList.size(); ++i ) + { + propertyNameList << QStringLiteral( "*" ); + } } - currentLayer = layerList.at( 0 ); + QMap fidsMap; - QgsVectorLayer *layer = qobject_cast( currentLayer ); - if ( layer && wfsLayersId.contains( layer->id() ) ) + QStringList::const_iterator fidIt = fidList.constBegin(); + QStringList::const_iterator propertyNameIt = propertyNameList.constBegin(); + for ( ; fidIt != fidList.constEnd(); ++fidIt ) { - expressionContext << QgsExpressionContextUtils::layerScope( layer ); - - //is there alias info for this vector layer? - QMap< int, QString > layerAliasInfo; - QgsStringMap aliasMap = layer->attributeAliases(); - QgsStringMap::const_iterator aliasIt = aliasMap.constBegin(); - for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt ) + // Get FeatureID + QString fid = *fidIt; + fid = fid.trimmed(); + // Get PropertyName for this FeatureID + QString propertyName; + if ( propertyNameIt != propertyNameList.constEnd() ) { - int attrIndex = layer->fields().lookupField( aliasIt.key() ); - if ( attrIndex != -1 ) - { - layerAliasInfo.insert( attrIndex, aliasIt.value() ); - } + propertyName = *propertyNameIt; + } + // testing typename in the WFS featureID + if ( !fid.contains( QLatin1String( "." ) ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) ); } - //excluded attributes for this layer - const QSet &layerExcludedAttributes = layer->excludeAttributesWfs(); - - //get layer precision - int layerPrec = configParser->wfsLayerPrecision( layer->id() ); - - //do a select with searchRect and go through all the features - QgsVectorDataProvider *provider = layer->dataProvider(); - if ( !provider ) + QString typeName = fid.section( QStringLiteral( "." ), 0, 0 ); + fid = fid.section( QStringLiteral( "." ), 1, 1 ); + if ( !typeNameList.contains( typeName ) ) { - errors << QStringLiteral( "The layer's provider for the TypeName '%1' is not found" ).arg( tnStr ); - continue; + typeNameList << typeName; } - QgsFeature feature; - - //map extent - searchRect = layer->extent(); - - //Using pending attributes and pending fields - QgsAttributeList attrIndexes = layer->pendingAllAttributesList(); - if ( propertyName != QLatin1String( "*" ) ) + // each Feature requested by FEATUREID can have each own property list + QString key = QStringLiteral( "%1(%2)" ).arg( typeName ).arg( propertyName ); + QgsFeatureIds fids; + if ( fidsMap.contains( key ) ) { + fids = fidsMap.value( key ); + } + fids.insert( fid.toInt() ); + fidsMap.insert( key, fids ); + + if ( propertyNameIt != propertyNameList.constEnd() ) + { + ++propertyNameIt; + } + } + + QMap::const_iterator fidsMapIt = fidsMap.constBegin(); + while ( fidsMapIt != fidsMap.constEnd() ) + { + QString key = fidsMapIt.key(); + + //Extract TypeName and PropertyName from key + QRegExp rx( "([^()]+)\\(([^()]+)\\)" ); + if ( rx.indexIn( key, 0 ) == -1 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Error getting properties for FEATUREID" ) ); + } + QString typeName = rx.cap( 1 ); + QString propertyName = rx.cap( 2 ); + + getFeatureQuery query; + query.typeName = typeName; + + // Parse PropertyName + if ( propertyName != QStringLiteral( "*" ) ) + { + QStringList propertyList; + QStringList attrList = propertyName.split( QStringLiteral( "," ) ); - if ( !attrList.isEmpty() ) + QStringList::const_iterator alstIt; + for ( alstIt = attrList.begin(); alstIt != attrList.end(); ++alstIt ) { - QStringList::const_iterator alstIt; - QList idxList; - QgsFields fields = layer->pendingFields(); - QString fieldName; - for ( alstIt = attrList.begin(); alstIt != attrList.end(); ++alstIt ) + QString fieldName = *alstIt; + fieldName = fieldName.trimmed(); + if ( fieldName.contains( QLatin1String( ":" ) ) ) { - fieldName = *alstIt; - int fieldNameIdx = fields.lookupField( fieldName ); - if ( fieldNameIdx > -1 ) + fieldName = fieldName.section( QStringLiteral( ":" ), 1, 1 ); + } + if ( fieldName.contains( QLatin1String( "/" ) ) ) + { + if ( fieldName.section( QStringLiteral( "/" ), 0, 0 ) != typeName ) { - idxList.append( fieldNameIdx ); + throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) ); } + fieldName = fieldName.section( QStringLiteral( "/" ), 1, 1 ); } - if ( !idxList.isEmpty() ) - { - attrIndexes = idxList; - } + propertyList.append( fieldName ); } + query.propertyList = propertyList; } - if ( bboxOk ) - searchRect.set( minx, miny, maxx, maxy ); - else - searchRect.set( searchRect.xMinimum() - 1. / pow( 10., layerPrec ), - searchRect.yMinimum() - 1. / pow( 10., layerPrec ), - searchRect.xMaximum() + 1. / pow( 10., layerPrec ), - searchRect.yMaximum() + 1. / pow( 10., layerPrec ) ); - layerCrs = layer->crs(); + QgsFeatureIds fids = fidsMapIt.value(); + QgsFeatureRequest featureRequest( fids ); - if ( featureIdOk ) + query.featureRequest = featureRequest; + request.queries.append( query ); + } + return request; + } + + if ( parameters.contains( QStringLiteral( "MAXFEATURES" ) ) ) + { + request.maxFeatures = parameters.value( QStringLiteral( "MAXFEATURES" ) ).toLong(); + } + if ( parameters.contains( QStringLiteral( "STARTINDEX" ) ) ) + { + request.startIndex = parameters.value( QStringLiteral( "STARTINDEX" ) ).toLong(); + } + + if ( !parameters.contains( QStringLiteral( "TYPENAME" ) ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is mandatory except if FEATUREID is used" ) ); + } + + typeNameList = parameters.value( QStringLiteral( "TYPENAME" ) ).split( QStringLiteral( "," ) ); + // Verifying the 1:1 mapping between TYPENAME and PROPERTYNAME + if ( !propertyNameList.isEmpty() && typeNameList.size() != propertyNameList.size() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the PROPERTYNAME list" ) ); + } + if ( propertyNameList.isEmpty() ) + { + for ( int i = 0; i < typeNameList.size(); ++i ) + { + propertyNameList << QStringLiteral( "*" ); + } + } + + // Create queries based on TypeName and propertyName + QStringList::const_iterator typeNameIt = typeNameList.constBegin(); + QStringList::const_iterator propertyNameIt = propertyNameList.constBegin(); + for ( ; typeNameIt != typeNameList.constEnd(); ++typeNameIt ) + { + QString typeName = *typeNameIt; + typeName = typeName.trimmed(); + // Get PropertyName for this typeName + QString propertyName; + if ( propertyNameIt != propertyNameList.constEnd() ) + { + propertyName = *propertyNameIt; + } + + getFeatureQuery query; + query.typeName = typeName; + + // Parse PropertyName + if ( propertyName != QStringLiteral( "*" ) ) + { + QStringList propertyList; + + QStringList attrList = propertyName.split( QStringLiteral( "," ) ); + QStringList::const_iterator alstIt; + for ( alstIt = attrList.begin(); alstIt != attrList.end(); ++alstIt ) { - Q_FOREACH ( const QString &fidStr, featureIdList ) + QString fieldName = *alstIt; + fieldName = fieldName.trimmed(); + if ( fieldName.contains( QLatin1String( ":" ) ) ) { - if ( !fidStr.startsWith( tnStr ) ) - continue; - //Need to be test for propertyname - layer->getFeatures( QgsFeatureRequest() - .setFilterFid( fidStr.section( QStringLiteral( "." ), 1, 1 ).toInt() ) - .setFlags( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) - .setSubsetOfAttributes( attrIndexes ) - ).nextFeature( feature ); - - if ( featureCounter == 0 ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - ++featureCounter; + fieldName = fieldName.section( QStringLiteral( ":" ), 1, 1 ); } + if ( fieldName.contains( QLatin1String( "/" ) ) ) + { + if ( fieldName.section( QStringLiteral( "/" ), 0, 0 ) != typeName ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) ); + } + fieldName = fieldName.section( QStringLiteral( "/" ), 1, 1 ); + } + propertyList.append( fieldName ); } - else if ( expFilterOk ) + query.propertyList = propertyList; + } + + request.queries.append( query ); + + if ( propertyNameIt != propertyNameList.constEnd() ) + { + ++propertyNameIt; + } + } + + // Manage extra parameter exp_filter + if ( parameters.contains( QStringLiteral( "EXP_FILTER" ) ) ) + { + QString expFilterName = parameters.value( QStringLiteral( "EXP_FILTER" ) ); + QStringList expFilterList; + QRegExp rx( "\\(([^()]+)\\)" ); + if ( rx.indexIn( expFilterName, 0 ) == -1 ) + { + expFilterList << expFilterName; + } + else + { + int pos = 0; + while ( ( pos = rx.indexIn( expFilterName, pos ) ) != -1 ) { - QgsFeatureRequest req; - if ( layer->wkbType() != QgsWkbTypes::NoGeometry ) + expFilterList << rx.cap( 1 ); + pos += rx.matchedLength(); + } + } + + // Verifying the 1:1 mapping between TYPENAME and EXP_FILTER but without exception + if ( request.queries.size() == expFilterList.size() ) + { + // set feature request filter expression based on filter element + QList::iterator qIt = request.queries.begin(); + QStringList::const_iterator expFilterIt = expFilterList.constBegin(); + for ( ; qIt != request.queries.end(); ++qIt ) + { + getFeatureQuery &query = *qIt; + // Get Filter for this typeName + QString expFilter; + if ( expFilterIt != expFilterList.constEnd() ) { - if ( bboxOk ) - { - req.setFilterRect( searchRect ); - req.setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - } - else - { - req.setFlags( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ); - } + expFilter = *expFilterIt; } - else - { - req.setFlags( QgsFeatureRequest::NoGeometry ); - withGeom = false; - } - req.setSubsetOfAttributes( attrIndexes ); - QgsFeatureIterator fit = layer->getFeatures( req ); std::shared_ptr filter( new QgsExpression( expFilter ) ); if ( filter ) { if ( filter->hasParserError() ) { - throw QgsRequestNotWellFormedException( QStringLiteral( "Expression filter error message: %1." ).arg( filter->parserErrorString() ) ); - } - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - expressionContext.setFeature( feature ); - QVariant res = filter->evaluate( &expressionContext ); - if ( filter->hasEvalError() ) - { - throw QgsRequestNotWellFormedException( QStringLiteral( "Expression filter eval error message: %1." ).arg( filter->evalErrorString() ) ); - } - if ( res.toInt() != 0 ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - } - } - else if ( filterOk ) - { - QDomElement filterElem = filter.firstChildElement(); - QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) ); - if ( !fidNodes.isEmpty() ) - { - QDomElement fidElem; - QString fid = QLatin1String( "" ); - for ( int f = 0; f < fidNodes.size(); f++ ) - { - fidElem = fidNodes.at( f ).toElement(); - fid = fidElem.attribute( QStringLiteral( "fid" ) ); - if ( fid.contains( QLatin1String( "." ) ) ) - { - if ( fid.section( QStringLiteral( "." ), 0, 0 ) != typeName ) - continue; - fid = fid.section( QStringLiteral( "." ), 1, 1 ); - } - - //Need to be test for propertyname - layer->getFeatures( QgsFeatureRequest() - .setFilterFid( fid.toInt() ) - .setFlags( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) - .setSubsetOfAttributes( attrIndexes ) - ).nextFeature( feature ); - - if ( featureCounter == 0 ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - - fid = QLatin1String( "" ); - ++featCounter; - ++featureCounter; - } - } - else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) ) - { - QDomElement bboxElem = filterElem.firstChildElement(); - QDomElement childElem = bboxElem.firstChildElement(); - - QgsFeatureRequest req; - req.setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - - while ( !childElem.isNull() ) - { - if ( childElem.tagName() == QLatin1String( "Box" ) ) - { - req.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) ); - } - else if ( childElem.tagName() != QLatin1String( "PropertyName" ) ) - { - QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem ); - req.setFilterRect( geom.boundingBox() ); - } - childElem = childElem.nextSiblingElement(); - } - req.setSubsetOfAttributes( attrIndexes ); - - QgsFeatureIterator fit = layer->getFeatures( req ); - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - else - { - std::shared_ptr filter( QgsOgcUtils::expressionFromOgcFilter( filterElem ) ); - if ( filter ) - { - if ( filter->hasParserError() ) - { - throw QgsRequestNotWellFormedException( QStringLiteral( "OGC expression filter error message: %1." ).arg( filter->parserErrorString() ) ); - } - QgsFeatureRequest req; - if ( layer->wkbType() != QgsWkbTypes::NoGeometry ) - { - if ( bboxOk ) - { - req.setFilterRect( searchRect ).setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); - } - else - { - req.setFlags( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ); - } - } - else - { - req.setFlags( QgsFeatureRequest::NoGeometry ); - withGeom = false; - } - req.setSubsetOfAttributes( attrIndexes ); - QgsFeatureIterator fit = layer->getFeatures( req ); - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - expressionContext.setFeature( feature ); - QVariant res = filter->evaluate( &expressionContext ); - if ( filter->hasEvalError() ) - { - throw QgsRequestNotWellFormedException( QStringLiteral( "OGC expression filter eval error message: %1." ).arg( filter->evalErrorString() ) ); - } - if ( res.toInt() != 0 ) - { - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } - } - } - } - } - else - { - //throw QgsMapServiceException( "RequestNotWellFormed", QString( "attrIndexes length: %1." ).arg( attrIndexes.count() ) ); - QgsFeatureRequest req; - if ( layer->wkbType() != QgsWkbTypes::NoGeometry ) - { - if ( bboxOk ) - { - req.setFilterRect( searchRect ).setFlags( QgsFeatureRequest::ExactIntersect | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ); + QgsMessageLog::logMessage( filter->parserErrorString() ); } else { - req.setFlags( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ); + if ( filter->needsGeometry() ) + { + query.featureRequest.setFlags( QgsFeatureRequest::NoFlags ); + } + query.featureRequest.setFilterExpression( filter->expression() ); } } - else - { - req.setFlags( QgsFeatureRequest::NoGeometry ); - withGeom = false; - } - req.setSubsetOfAttributes( attrIndexes ); - QgsFeatureIterator fit = layer->getFeatures( req ); - while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) ) - { - errors << QStringLiteral( "The feature %2 of layer for the TypeName '%1'" ).arg( tnStr ).arg( featureCounter ); - if ( featureCounter == startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - - if ( featureCounter >= startIndex ) - { - setGetFeature( response, format, &feature, featCounter, layerPrec, layerCrs, attrIndexes, layerExcludedAttributes, - typeName, withGeom, geometryName ); - ++featCounter; - } - ++featureCounter; - } } - } else { - errors << QStringLiteral( "The layer for the TypeName '%1' is not a WFS layer" ).arg( tnStr ); + QgsMessageLog::logMessage( "There has to be a 1:1 mapping between each element in a TYPENAME and the EXP_FILTER list" ); } - } - QgsProject::instance()->removeAllMapLayers(); - if ( featureCounter <= startIndex ) - startGetFeature( request, response, project, format, layerPrec, layerCrs, &searchRect, typeNames ); - endGetFeature( response, format ); + if ( parameters.contains( QStringLiteral( "BBOX" ) ) ) + { + // get bbox value + QString bbox = parameters.value( QStringLiteral( "BBOX" ) ); + if ( bbox.isEmpty() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX parameter is empty" ) ); + } + // get bbox corners + QStringList corners = bbox.split( "," ); + if ( corners.size() != 4 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX has to be composed of 4 elements: '%1'" ).arg( bbox ) ); + } + + // convert corners to double + double d[4]; + bool ok; + for ( int i = 0; i < 4; i++ ) + { + corners[i].replace( QLatin1String( " " ), QLatin1String( "+" ) ); + d[i] = corners[i].toDouble( &ok ); + if ( !ok ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX has to be composed of 4 double: '%1'" ).arg( bbox ) ); + } + } + // create extent + QgsRectangle extent( d[0], d[1], d[2], d[3] ); + + // set feature request filter rectangle + QList::iterator qIt = request.queries.begin(); + for ( ; qIt != request.queries.end(); ++qIt ) + { + getFeatureQuery &query = *qIt; + query.featureRequest.setFilterRect( extent ); + } + return request; + } + else if ( parameters.contains( QStringLiteral( "FILTER" ) ) ) + { + QString filterName = parameters.value( QStringLiteral( "FILTER" ) ); + QStringList filterList; + QRegExp rx( "\\(([^()]+)\\)" ); + if ( rx.indexIn( filterName, 0 ) == -1 ) + { + filterList << filterName; + } + else + { + int pos = 0; + while ( ( pos = rx.indexIn( filterName, pos ) ) != -1 ) + { + filterList << rx.cap( 1 ); + pos += rx.matchedLength(); + } + } + + // Verifying the 1:1 mapping between TYPENAME and FILTER + if ( request.queries.size() != filterList.size() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the FILTER list" ) ); + } + + // set feature request filter expression based on filter element + QList::iterator qIt = request.queries.begin(); + QStringList::const_iterator filterIt = filterList.constBegin(); + for ( ; qIt != request.queries.end(); ++qIt ) + { + getFeatureQuery &query = *qIt; + // Get Filter for this typeName + QDomDocument filter; + if ( filterIt != filterList.constEnd() ) + { + QString errorMsg; + if ( !filter.setContent( *filterIt, true, &errorMsg ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, *filterIt ) ); + } + } + + QDomElement filterElem = filter.firstChildElement(); + query.featureRequest = parseFilterElement( query.typeName, filterElem ); + + if ( filterIt != filterList.constEnd() ) + { + ++filterIt; + } + } + return request; + } + + return request; + } + + getFeatureRequest parseGetFeatureRequestBody( QDomElement &docElem ) + { + getFeatureRequest request; + request.maxFeatures = -1; + request.startIndex = 0; + request.outputFormat = "GML2"; + + if ( docElem.hasAttribute( QStringLiteral( "maxFeatures" ) ) ) + { + request.maxFeatures = docElem.attribute( QStringLiteral( "maxFeatures" ) ).toLong(); + } + if ( docElem.hasAttribute( QStringLiteral( "startIndex" ) ) ) + { + request.startIndex = docElem.attribute( QStringLiteral( "startIndex" ) ).toLong(); + } + if ( docElem.hasAttribute( QStringLiteral( "outputFormat" ) ) ) + { + request.outputFormat = docElem.attribute( QStringLiteral( "outputFormat" ) ); + } + + QDomNodeList queryNodes = docElem.elementsByTagName( QStringLiteral( "Query" ) ); + QDomElement queryElem; + for ( int i = 0; i < queryNodes.size(); i++ ) + { + queryElem = queryNodes.at( i ).toElement(); + getFeatureQuery query = parseQueryElement( queryElem ); + request.queries.append( query ); + } + return request; + } + + getFeatureQuery parseQueryElement( QDomElement &queryElem ) + { + QString typeName = queryElem.attribute( QStringLiteral( "typeName" ), QLatin1String( "" ) ); + if ( typeName.contains( QLatin1String( ":" ) ) ) + { + typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); + } + + QgsFeatureRequest featureRequest; + QStringList propertyList; + QDomNodeList queryChildNodes = queryElem.childNodes(); + if ( queryChildNodes.size() ) + { + for ( int q = 0; q < queryChildNodes.size(); q++ ) + { + QDomElement queryChildElem = queryChildNodes.at( q ).toElement(); + if ( queryChildElem.tagName() == QLatin1String( "PropertyName" ) ) + { + QString fieldName = queryChildElem.text().trimmed(); + if ( fieldName.contains( QLatin1String( ":" ) ) ) + { + fieldName = fieldName.section( QStringLiteral( ":" ), 1, 1 ); + } + if ( fieldName.contains( QLatin1String( "/" ) ) ) + { + if ( fieldName.section( QStringLiteral( "/" ), 0, 0 ) != typeName ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) ); + } + fieldName = fieldName.section( QStringLiteral( "/" ), 1, 1 ); + } + propertyList.append( fieldName ); + } + else if ( queryChildElem.tagName() == QLatin1String( "Filter" ) ) + { + featureRequest = parseFilterElement( typeName, queryChildElem ); + } + } + } + + getFeatureQuery query; + query.typeName = typeName; + query.featureRequest = featureRequest; + query.propertyList = propertyList; + return query; } namespace diff --git a/src/server/services/wfs/qgswfsgetfeature.h b/src/server/services/wfs/qgswfsgetfeature.h index 778f9dc45d0..1bddc625646 100644 --- a/src/server/services/wfs/qgswfsgetfeature.h +++ b/src/server/services/wfs/qgswfsgetfeature.h @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -24,6 +25,39 @@ namespace QgsWfs { + struct getFeatureQuery + { + QString typeName; + + QgsFeatureRequest featureRequest; + + QStringList propertyList; + }; + + struct getFeatureRequest + { + long maxFeatures; + + long startIndex; + + QString outputFormat; + + QList< getFeatureQuery > queries; + + QString geometryName; + }; + + /** Transform Query element to getFeatureQuery + */ + getFeatureQuery parseQueryElement( QDomElement &queryElem ); + + /** Transform RequestBody root element to getFeatureRequest + */ + getFeatureRequest parseGetFeatureRequestBody( QDomElement &docElem ); + + /** Transform parameters to getFeatureRequest + */ + getFeatureRequest parseGetFeatureParameters( QgsServerRequest::Parameters parameters ); /** Output WFS GetFeature response */ diff --git a/src/server/services/wfs/qgswfstransaction.cpp b/src/server/services/wfs/qgswfstransaction.cpp index 2c003bed088..84783bf1e8f 100644 --- a/src/server/services/wfs/qgswfstransaction.cpp +++ b/src/server/services/wfs/qgswfstransaction.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -19,8 +20,15 @@ * * ***************************************************************************/ #include "qgswfsutils.h" +#include "qgsserverprojectutils.h" +#include "qgsfields.h" +#include "qgsexpression.h" #include "qgsgeometry.h" +#include "qgsmaplayer.h" +#include "qgsfeatureiterator.h" #include "qgsvectordataprovider.h" +#include "qgsvectorlayer.h" +#include "qgsfilterrestorer.h" #include "qgsogcutils.h" #include "qgswfstransaction.h" @@ -30,96 +38,49 @@ namespace QgsWfs { void addTransactionResult( QDomDocument &responseDoc, QDomElement &responseElem, const QString &status, const QString &locator, const QString &message ); - - - QgsFeatureIds getFeatureIdsFromFilter( const QDomElement &filterElem, QgsVectorLayer *layer ); - } - void writeTransaction( QgsServerInterface *serverIface, const QString &version, - const QgsServerRequest &request, QgsServerResponse &response ) + void writeTransaction( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ) { - QDomDocument doc = createTransactionDocument( serverIface, version, request ); + QDomDocument doc = createTransactionDocument( serverIface, project, version, request ); response.setHeader( "Content-Type", "text/xml; charset=utf-8" ); response.write( doc.toByteArray() ); } - - QDomDocument createTransactionDocument( QgsServerInterface *serverIface, const QString &version, - const QgsServerRequest &request ) + QDomDocument createTransactionDocument( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request ) { Q_UNUSED( version ); + QgsServerRequest::Parameters parameters = request.parameters(); + transactionRequest aRequest; + QDomDocument doc; - - QgsWfsProjectParser *configParser = getConfigParser( serverIface ); -#ifdef HAVE_SERVER_PYTHON_PLUGINS - QgsAccessControl *accessControl = serverIface->accessControls(); -#endif - const QString requestBody = request.getParameter( QStringLiteral( "REQUEST_BODY" ) ); - QString errorMsg; - if ( !doc.setContent( requestBody, true, &errorMsg ) ) + + if ( doc.setContent( parameters.value( QStringLiteral( "REQUEST_BODY" ) ), true, &errorMsg ) ) { - throw QgsRequestNotWellFormedException( errorMsg ); + QDomElement docElem = doc.documentElement(); + aRequest = parseTransactionRequestBody( docElem ); + } + else + { + aRequest = parseTransactionParameters( parameters ); } - QDomElement docElem = doc.documentElement(); - QDomNodeList docChildNodes = docElem.childNodes(); - - // Re-organize the transaction document - QDomDocument mDoc; - QDomElement mDocElem = mDoc.createElement( QStringLiteral( "myTransactionDocument" ) ); - mDocElem.setAttribute( QStringLiteral( "xmlns" ), QGS_NAMESPACE ); - mDocElem.setAttribute( QStringLiteral( "xmlns:wfs" ), WFS_NAMESPACE ); - mDocElem.setAttribute( QStringLiteral( "xmlns:gml" ), GML_NAMESPACE ); - mDocElem.setAttribute( QStringLiteral( "xmlns:ogc" ), OGC_NAMESPACE ); - mDocElem.setAttribute( QStringLiteral( "xmlns:qgs" ), QGS_NAMESPACE ); - mDocElem.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); - mDoc.appendChild( mDocElem ); - - QDomElement actionElem; - QString actionName; - QDomElement typeNameElem; - QString typeName; - - for ( int i = docChildNodes.count(); 0 < i; --i ) + int actionCount = aRequest.inserts.size() + aRequest.updates.size() + aRequest.deletes.size(); + if ( actionCount == 0 ) { - actionElem = docChildNodes.at( i - 1 ).toElement(); - actionName = actionElem.localName(); - - if ( actionName == QLatin1String( "Insert" ) ) - { - QDomElement featureElem = actionElem.firstChild().toElement(); - typeName = featureElem.localName(); - } - else if ( actionName == QLatin1String( "Update" ) ) - { - typeName = actionElem.attribute( QStringLiteral( "typeName" ) ); - } - else if ( actionName == QLatin1String( "Delete" ) ) - { - typeName = actionElem.attribute( QStringLiteral( "typeName" ) ); - } - - if ( typeName.contains( QLatin1String( ":" ) ) ) - typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); - - QDomNodeList typeNameList = mDocElem.elementsByTagName( typeName ); - if ( typeNameList.count() == 0 ) - { - typeNameElem = mDoc.createElement( typeName ); - mDocElem.appendChild( typeNameElem ); - } - else - typeNameElem = typeNameList.at( 0 ).toElement(); - - typeNameElem.appendChild( actionElem ); + throw QgsRequestNotWellFormedException( QStringLiteral( "No actions found" ) ); } + performTransaction( aRequest, serverIface, project ); + // It's time to make the transaction // Create the response document QDomDocument resp; @@ -132,365 +93,1072 @@ namespace QgsWfs respElem.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.0.0" ) ); resp.appendChild( respElem ); - // Store the created feature id for WFS - QStringList insertResults; - // Get the WFS layers id - QStringList wfsLayersId = configParser->wfsLayers();; + int errorCount = 0; + QStringList errorLocators; + QStringList errorMessages; - QList layerList; - QgsMapLayer *currentLayer = nullptr; - - // Loop through the layer transaction elements - docChildNodes = mDocElem.childNodes(); - for ( int i = 0; i < docChildNodes.count(); ++i ) + QList::iterator tuIt = aRequest.updates.begin(); + for ( ; tuIt != aRequest.updates.end(); ++tuIt ) { - // Get the vector layer - typeNameElem = docChildNodes.at( i ).toElement(); - typeName = typeNameElem.tagName(); - - layerList = configParser->mapLayerFromTypeName( typeName ); - // Could be empty! - if ( layerList.count() > 0 ) + transactionUpdate &action = *tuIt; + if ( action.error ) { - currentLayer = layerList.at( 0 ); + errorCount += 1; + if ( action.handle.isEmpty() ) + { + errorLocators << QStringLiteral( "Update:%1" ).arg( action.typeName ); + } + else + { + errorLocators << action.handle; + } + errorMessages << action.errorMsg; + } + } + + QList::iterator tdIt = aRequest.deletes.begin(); + for ( ; tdIt != aRequest.deletes.end(); ++tdIt ) + { + transactionDelete &action = *tdIt; + if ( action.error ) + { + errorCount += 1; + if ( action.handle.isEmpty() ) + { + errorLocators << QStringLiteral( "Delete:%1" ).arg( action.typeName ); + } + else + { + errorLocators << action.handle; + } + errorMessages << action.errorMsg; + } + } + + QList::iterator tiIt = aRequest.inserts.begin(); + for ( ; tiIt != aRequest.inserts.end(); ++tiIt ) + { + transactionInsert &action = *tiIt; + if ( action.error ) + { + errorCount += 1; + if ( action.handle.isEmpty() ) + { + errorLocators << QStringLiteral( "Insert:%1" ).arg( action.typeName ); + } + else + { + errorLocators << action.handle; + } + errorMessages << action.errorMsg; } else { - throw QgsRequestNotWellFormedException( QStringLiteral( "Wrong TypeName: %1" ).arg( typeName ) ); + QStringList::const_iterator fidIt = action.insertFeatureIds.constBegin(); + for ( ; fidIt != action.insertFeatureIds.constEnd(); ++fidIt ) + { + QString fidStr = *fidIt; + QDomElement irElem = doc.createElement( QStringLiteral( "InsertResult" ) ); + if ( !action.handle.isEmpty() ) + { + irElem.setAttribute( QStringLiteral( "handle" ), action.handle ); + } + QDomElement fiElem = doc.createElement( QStringLiteral( "ogc:FeatureId" ) ); + fiElem.setAttribute( QStringLiteral( "fid" ), fidStr ); + irElem.appendChild( fiElem ); + respElem.appendChild( irElem ); + } + } + } + + // addTransactionResult + if ( errorCount == 0 ) + { + addTransactionResult( resp, respElem, QStringLiteral( "SUCCESS" ), QString(), QString() ); + } + else + { + QString locator = errorLocators.join( QStringLiteral( "; " ) ); + QString message = errorMessages.join( QStringLiteral( "; " ) ); + if ( errorCount != actionCount ) + { + addTransactionResult( resp, respElem, QStringLiteral( "PARTIAL" ), locator, message ); + } + else + { + addTransactionResult( resp, respElem, QStringLiteral( "ERROR" ), locator, message ); + } + } + return resp; + } + + void performTransaction( transactionRequest &aRequest, QgsServerInterface *serverIface, const QgsProject *project ) + { + // store typeName + QStringList typeNameList; + + QList::iterator tiIt = aRequest.inserts.begin(); + for ( ; tiIt != aRequest.inserts.end(); ++tiIt ) + { + QString name = ( *tiIt ).typeName; + if ( !typeNameList.contains( name ) ) + typeNameList << name; + } + QList::iterator tuIt = aRequest.updates.begin(); + for ( ; tuIt != aRequest.updates.end(); ++tuIt ) + { + QString name = ( *tuIt ).typeName; + if ( !typeNameList.contains( name ) ) + typeNameList << name; + } + QList::iterator tdIt = aRequest.deletes.begin(); + for ( ; tdIt != aRequest.deletes.end(); ++tdIt ) + { + QString name = ( *tdIt ).typeName; + if ( !typeNameList.contains( name ) ) + typeNameList << name; + } + + // get access controls + QgsAccessControl *accessControl = serverIface->accessControls(); + + //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope + //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually + std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer( accessControl ) ); + + // get layers + QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + QStringList wfstUpdateLayerIds = QgsServerProjectUtils::wfstUpdateLayerIds( *project ); + QStringList wfstDeleteLayerIds = QgsServerProjectUtils::wfstDeleteLayerIds( *project ); + QStringList wfstInsertLayerIds = QgsServerProjectUtils::wfstInsertLayerIds( *project ); + QMap mapLayerMap; + for ( int i = 0; i < wfsLayerIds.size(); ++i ) + { + QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) ); + if ( layer->type() != QgsMapLayer::LayerType::VectorLayer ) + { + continue; } - QgsVectorLayer *layer = qobject_cast( currentLayer ); - // it's a vectorlayer and defined by the administrator as a WFS layer - if ( layer && wfsLayersId.contains( layer->id() ) ) + QString name = layer->name(); + if ( !layer->shortName().isEmpty() ) + name = layer->shortName(); + name = name.replace( QLatin1String( " " ), QLatin1String( "_" ) ); + + if ( !typeNameList.contains( name ) ) { -#ifdef HAVE_SERVER_PYTHON_PLUGINS - if ( actionName == QLatin1String( "Insert" ) ) + continue; + } + + // get vector layer + QgsVectorLayer *vlayer = qobject_cast( layer ); + if ( !vlayer ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Layer error on '%1'" ).arg( name ) ); + } + + //get provider + QgsVectorDataProvider *provider = vlayer->dataProvider(); + if ( !provider ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Provider error on layer '%1'" ).arg( name ) ); + } + + // get provider capabilities + int cap = provider->capabilities(); + if ( !( cap & QgsVectorDataProvider::ChangeAttributeValues ) && !( cap & QgsVectorDataProvider::ChangeGeometries ) + && !( cap & QgsVectorDataProvider::DeleteFeatures ) && !( cap & QgsVectorDataProvider::AddFeatures ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "No capabilities to do WFS changes on layer '%1'" ).arg( name ) ); + } + + if ( !wfstUpdateLayerIds.contains( vlayer->id() ) + && !wfstDeleteLayerIds.contains( vlayer->id() ) + && !wfstInsertLayerIds.contains( vlayer->id() ) ) + { + throw QgsSecurityAccessException( QStringLiteral( "No permissions to do WFS changes on layer '%1'" ).arg( name ) ); + } + if ( accessControl && !accessControl->layerUpdatePermission( vlayer ) + && !accessControl->layerDeletePermission( vlayer ) && !accessControl->layerInsertPermission( vlayer ) ) + { + throw QgsSecurityAccessException( QStringLiteral( "No permissions to do WFS changes on layer '%1'" ).arg( name ) ); + } + + if ( accessControl ) + { + QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, vlayer, filterRestorer->originalFilters() ); + } + + // store layers + mapLayerMap[name] = vlayer; + } + + // perform updates + tuIt = aRequest.updates.begin(); + for ( ; tuIt != aRequest.updates.end(); ++tuIt ) + { + transactionUpdate &action = *tuIt; + QString typeName = action.typeName; + + if ( !mapLayerMap.keys().contains( typeName ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "TypeName '%1' unknown" ).arg( typeName ); + continue; + } + + // get vector layer + QgsVectorLayer *vlayer = mapLayerMap[typeName]; + + // verifying specific permissions + if ( !wfstUpdateLayerIds.contains( vlayer->id() ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS updates on layer '%1'" ).arg( typeName ); + continue; + } + if ( accessControl && !accessControl->layerUpdatePermission( vlayer ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS updates on layer '%1'" ).arg( typeName ); + continue; + } + + //get provider + QgsVectorDataProvider *provider = vlayer->dataProvider(); + + // verifying specific capabilities + int cap = provider->capabilities(); + if ( !( cap & QgsVectorDataProvider::ChangeAttributeValues ) || !( cap & QgsVectorDataProvider::ChangeGeometries ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No capabilities to do WFS updates on layer '%1'" ).arg( typeName ); + continue; + } + // start editing + vlayer->startEditing(); + + // update request + QgsFeatureRequest featureRequest = action.featureRequest; + + // expression context + QgsExpressionContext expressionContext; + expressionContext << QgsExpressionContextUtils::globalScope() + << QgsExpressionContextUtils::projectScope( project ) + << QgsExpressionContextUtils::layerScope( vlayer ); + featureRequest.setExpressionContext( expressionContext ); + + if ( accessControl ) + { + accessControl->filterFeatures( vlayer, featureRequest ); + } + + // get iterator + QgsFeatureIterator fit = vlayer->getFeatures( featureRequest ); + QgsFeature feature; + // get action properties + QMap propertyMap = action.propertyMap; + QDomElement geometryElem = action.geometryElement; + // get field information + QgsFields fields = provider->fields(); + QMap fieldMap = provider->fieldNameMap(); + QMap::const_iterator fieldMapIt; + QString fieldName; + bool conversionSuccess; + // Update the features + while ( fit.nextFeature( feature ) ) + { + if ( accessControl && !accessControl->allowToEdit( vlayer, feature ) ) { - if ( !accessControl->layerInsertPermission( layer ) ) + action.error = true; + action.errorMsg = QStringLiteral( "Feature modify permission denied on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; + } + QMap< QString, QString >::const_iterator it = propertyMap.constBegin(); + for ( ; it != propertyMap.constEnd(); ++it ) + { + fieldName = it.key(); + fieldMapIt = fieldMap.find( fieldName ); + if ( fieldMapIt == fieldMap.constEnd() ) { - throw QgsSecurityAccessException( QStringLiteral( "Feature insert permission denied" ) ); + continue; } - } - else if ( actionName == QLatin1String( "Update" ) ) - { - if ( !accessControl->layerUpdatePermission( layer ) ) + QgsField field = fields.at( fieldMapIt.value() ); + QVariant value = it.value(); + if ( field.type() == 2 ) { - throw QgsSecurityAccessException( QStringLiteral( "Feature update permission denied" ) ); - } - } - else if ( actionName == QLatin1String( "Delete" ) ) - { - if ( !accessControl->layerDeletePermission( layer ) ) - { - throw QgsSecurityAccessException( QStringLiteral( "Feature delete permission denied" ) ); - } - } -#endif - - // Get the provider and it's capabilities - QgsVectorDataProvider *provider = layer->dataProvider(); - if ( !provider ) - { - continue; - } - - int cap = provider->capabilities(); - - // Start the update transaction - layer->startEditing(); - if ( ( cap & QgsVectorDataProvider::ChangeAttributeValues ) && ( cap & QgsVectorDataProvider::ChangeGeometries ) ) - { - // Loop through the update elements for this layer - QDomNodeList upNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Update" ) ); - for ( int j = 0; j < upNodeList.count(); ++j ) - { - if ( !configParser->wfstUpdateLayers().contains( layer->id() ) ) + value = it.value().toInt( &conversionSuccess ); + if ( !conversionSuccess ) { - //no wfs permissions to do updates - QString errorMsg = "No permissions to do WFS updates on layer '" + layer->name() + "'"; - QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL ); - addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Update" ), errorMsg ); - return resp; - } - - actionElem = upNodeList.at( j ).toElement(); - - // Get the Feature Ids for this filter on the layer - QDomElement filterElem = actionElem.elementsByTagName( QStringLiteral( "Filter" ) ).at( 0 ).toElement(); - QgsFeatureIds fids = getFeatureIdsFromFilter( filterElem, layer ); - - // Loop through the property elements - // Store properties and the geometry element - QDomNodeList propertyNodeList = actionElem.elementsByTagName( QStringLiteral( "Property" ) ); - QMap propertyMap; - QDomElement propertyElem; - QDomElement nameElem; - QDomElement valueElem; - QDomElement geometryElem; - - for ( int l = 0; l < propertyNodeList.count(); ++l ) - { - propertyElem = propertyNodeList.at( l ).toElement(); - nameElem = propertyElem.elementsByTagName( QStringLiteral( "Name" ) ).at( 0 ).toElement(); - valueElem = propertyElem.elementsByTagName( QStringLiteral( "Value" ) ).at( 0 ).toElement(); - if ( nameElem.text() != QLatin1String( "geometry" ) ) - { - propertyMap.insert( nameElem.text(), valueElem.text() ); - } - else - { - geometryElem = valueElem; - } - } - - // Update the features - QgsFields fields = provider->fields(); - QMap fieldMap = provider->fieldNameMap(); - QMap::const_iterator fieldMapIt; - QString fieldName; - bool conversionSuccess; - - QgsFeatureIds::const_iterator fidIt = fids.constBegin(); - for ( ; fidIt != fids.constEnd(); ++fidIt ) - { -#ifdef HAVE_SERVER_PYTHON_PLUGINS - QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) ); - QgsFeature feature; - while ( fit.nextFeature( feature ) ) - { - if ( !accessControl->allowToEdit( layer, feature ) ) - { - throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) ); - } - } -#endif - - QMap< QString, QString >::const_iterator it = propertyMap.constBegin(); - for ( ; it != propertyMap.constEnd(); ++it ) - { - fieldName = it.key(); - fieldMapIt = fieldMap.find( fieldName ); - if ( fieldMapIt == fieldMap.constEnd() ) - { - continue; - } - QgsField field = fields.at( fieldMapIt.value() ); - if ( field.type() == 2 ) - layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value().toInt( &conversionSuccess ) ); - else if ( field.type() == 6 ) - layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value().toDouble( &conversionSuccess ) ); - else - layer->changeAttributeValue( *fidIt, fieldMapIt.value(), it.value() ); - } - - if ( !geometryElem.isNull() ) - { - QgsGeometry g = QgsOgcUtils::geometryFromGML( geometryElem ); - if ( !layer->changeGeometry( *fidIt, g ) ) - { - throw QgsRequestNotWellFormedException( QStringLiteral( "Error in change geometry" ) ); - } - } - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) ); - while ( fit.nextFeature( feature ) ) - { - if ( !accessControl->allowToEdit( layer, feature ) ) - { - layer->rollBack(); - throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) ); - } - } -#endif + action.error = true; + action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; } } - } - // Commit the changes of the update elements - if ( !layer->commitChanges() ) - { - addTransactionResult( resp, respElem, QStringLiteral( "PARTIAL" ), QStringLiteral( "Update" ), layer->commitErrors().join( QStringLiteral( "\n " ) ) ); - return resp; - } - // Start the delete transaction - layer->startEditing(); - if ( ( cap & QgsVectorDataProvider::DeleteFeatures ) ) - { - // Loop through the delete elements - QDomNodeList delNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Delete" ) ); - for ( int j = 0; j < delNodeList.count(); ++j ) + else if ( field.type() == 6 ) { - if ( !configParser->wfstDeleteLayers().contains( layer->id() ) ) + value = it.value().toDouble( &conversionSuccess ); + if ( !conversionSuccess ) { - //no wfs permissions to do updates - QString errorMsg = "No permissions to do WFS deletes on layer '" + layer->name() + "'"; - QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL ); - addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Delete" ), errorMsg ); - return resp; - } - - actionElem = delNodeList.at( j ).toElement(); - QDomElement filterElem = actionElem.firstChild().toElement(); - // Get Feature Ids for the Filter element - QgsFeatureIds fids = getFeatureIdsFromFilter( filterElem, layer ); - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - QgsFeatureIds::const_iterator fidIt = fids.constBegin(); - for ( ; fidIt != fids.constEnd(); ++fidIt ) - { - QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest( *fidIt ) ); - QgsFeature feature; - while ( fit.nextFeature( feature ) ) - { - if ( !accessControl->allowToEdit( layer, feature ) ) - { - throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) ); - } - } - } -#endif - - layer->selectByIds( fids ); - layer->deleteSelectedFeatures(); - } - } - // Commit the changes of the delete elements - if ( !layer->commitChanges() ) - { - addTransactionResult( resp, respElem, QStringLiteral( "PARTIAL" ), QStringLiteral( "Delete" ), layer->commitErrors().join( QStringLiteral( "\n " ) ) ); - return resp; - } - - // Store the inserted features - QgsFeatureList inFeatList; - if ( cap & QgsVectorDataProvider::AddFeatures ) - { - // Get Layer Field Information - QgsFields fields = provider->fields(); - QMap fieldMap = provider->fieldNameMap(); - QMap::const_iterator fieldMapIt; - - // Loop through the insert elements - QDomNodeList inNodeList = typeNameElem.elementsByTagNameNS( WFS_NAMESPACE, QStringLiteral( "Insert" ) ); - for ( int j = 0; j < inNodeList.count(); ++j ) - { - if ( !configParser->wfstInsertLayers().contains( layer->id() ) ) - { - //no wfs permissions to do updates - QString errorMsg = "No permissions to do WFS inserts on layer '" + layer->name() + "'"; - QgsMessageLog::logMessage( errorMsg, QStringLiteral( "Server" ), QgsMessageLog::CRITICAL ); - addTransactionResult( resp, respElem, QStringLiteral( "FAILED" ), QStringLiteral( "Insert" ), errorMsg ); - return resp; - } - - actionElem = inNodeList.at( j ).toElement(); - // Loop through the feature element - QDomNodeList featNodes = actionElem.childNodes(); - for ( int l = 0; l < featNodes.count(); l++ ) - { - // Add the feature to the layer - // and store it to put it's Feature Id in the response - inFeatList << QgsFeature( fields ); - - // Create feature for this layer - QDomElement featureElem = featNodes.at( l ).toElement(); - - QDomNode currentAttributeChild = featureElem.firstChild(); - - while ( !currentAttributeChild.isNull() ) - { - QDomElement currentAttributeElement = currentAttributeChild.toElement(); - QString attrName = currentAttributeElement.localName(); - - if ( attrName != QLatin1String( "boundedBy" ) ) - { - if ( attrName != QLatin1String( "geometry" ) ) //a normal attribute - { - fieldMapIt = fieldMap.find( attrName ); - if ( fieldMapIt == fieldMap.constEnd() ) - { - continue; - } - QgsField field = fields.at( fieldMapIt.value() ); - QString attrValue = currentAttributeElement.text(); - int attrType = field.type(); - QgsMessageLog::logMessage( QStringLiteral( "attr: name=%1 idx=%2 value=%3" ).arg( attrName ).arg( fieldMapIt.value() ).arg( attrValue ) ); - if ( attrType == QVariant::Int ) - inFeatList.last().setAttribute( fieldMapIt.value(), attrValue.toInt() ); - else if ( attrType == QVariant::Double ) - inFeatList.last().setAttribute( fieldMapIt.value(), attrValue.toDouble() ); - else - inFeatList.last().setAttribute( fieldMapIt.value(), attrValue ); - } - else //a geometry attribute - { - QgsGeometry g = QgsOgcUtils::geometryFromGML( currentAttributeElement ); - inFeatList.last().setGeometry( g ); - } - } - currentAttributeChild = currentAttributeChild.nextSibling(); - } + action.error = true; + action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; } } + vlayer->changeAttributeValue( feature.id(), fieldMapIt.value(), value ); } -#ifdef HAVE_SERVER_PYTHON_PLUGINS - QgsFeatureList::iterator featureIt = inFeatList.begin(); - while ( featureIt != inFeatList.end() ) + if ( action.error ) { - if ( !accessControl->allowToEdit( layer, *featureIt ) ) + break; + } + + if ( !geometryElem.isNull() ) + { + QgsGeometry g = QgsOgcUtils::geometryFromGML( geometryElem ); + if ( g.isNull() ) { - throw QgsSecurityAccessException( QStringLiteral( "Feature modify permission denied" ) ); + action.error = true; + action.errorMsg = QStringLiteral( "Geometry from GML error on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; + } + if ( !vlayer->changeGeometry( feature.id(), g ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Error in change geometry on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; + } + } + } + if ( action.error ) + { + continue; + } + // verifying changes + if ( accessControl ) + { + fit = vlayer->getFeatures( featureRequest ); + while ( fit.nextFeature( feature ) ) + { + if ( accessControl && !accessControl->allowToEdit( vlayer, feature ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Feature modify permission denied on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; + } + } + } + if ( action.error ) + { + continue; + } + + // Commit the changes of the update elements + if ( !vlayer->commitChanges() ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Error committing updates: %1" ).arg( vlayer->commitErrors().join( QStringLiteral( "; " ) ) ); + vlayer->rollBack(); + continue; + } + // all the changes are OK! + action.error = false; + + } + + // perform deletes + tdIt = aRequest.deletes.begin(); + for ( ; tdIt != aRequest.deletes.end(); ++tdIt ) + { + transactionDelete &action = *tdIt; + QString typeName = action.typeName; + + if ( !mapLayerMap.keys().contains( typeName ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "TypeName '%1' unknown" ).arg( typeName ); + continue; + } + + // get vector layer + QgsVectorLayer *vlayer = mapLayerMap[typeName]; + + // verifying specific permissions + if ( !wfstDeleteLayerIds.contains( vlayer->id() ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS deletes on layer '%1'" ).arg( typeName ); + continue; + } + if ( accessControl && !accessControl->layerDeletePermission( vlayer ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS deletes on layer '%1'" ).arg( typeName ); + continue; + } + + //get provider + QgsVectorDataProvider *provider = vlayer->dataProvider(); + + // verifying specific capabilities + int cap = provider->capabilities(); + if ( !( cap & QgsVectorDataProvider::DeleteFeatures ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No capabilities to do WFS deletes on layer '%1'" ).arg( typeName ); + continue; + } + // start editing + vlayer->startEditing(); + + // update request + QgsFeatureRequest featureRequest = action.featureRequest; + + // expression context + QgsExpressionContext expressionContext; + expressionContext << QgsExpressionContextUtils::globalScope() + << QgsExpressionContextUtils::projectScope( project ) + << QgsExpressionContextUtils::layerScope( vlayer ); + featureRequest.setExpressionContext( expressionContext ); + + // get iterator + QgsFeatureIterator fit = vlayer->getFeatures( featureRequest ); + QgsFeature feature; + // get deleted fids + QgsFeatureIds fids; + while ( fit.nextFeature( feature ) ) + { + if ( accessControl && !accessControl->allowToEdit( vlayer, feature ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Feature modify permission denied" ); + vlayer->rollBack(); + break; + } + fids << feature.id(); + } + if ( action.error ) + { + continue; + } + // delete features + if ( !vlayer->deleteFeatures( fids ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Delete features failed on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + continue; + } + + // Commit the changes of the update elements + if ( !vlayer->commitChanges() ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Error committing deletes: %1" ).arg( vlayer->commitErrors().join( QStringLiteral( "; " ) ) ); + vlayer->rollBack(); + continue; + } + // all the changes are OK! + action.error = false; + } + + // perform inserts + tiIt = aRequest.inserts.begin(); + for ( ; tiIt != aRequest.inserts.end(); ++tiIt ) + { + transactionInsert &action = *tiIt; + QString typeName = action.typeName; + + if ( !mapLayerMap.keys().contains( typeName ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "TypeName '%1' unknown" ).arg( typeName ); + continue; + } + + // get vector layer + QgsVectorLayer *vlayer = mapLayerMap[typeName]; + + // verifying specific permissions + if ( !wfstInsertLayerIds.contains( vlayer->id() ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS inserts on layer '%1'" ).arg( typeName ); + continue; + } + if ( accessControl && !accessControl->layerDeletePermission( vlayer ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No permissions to do WFS inserts on layer '%1'" ).arg( typeName ); + continue; + } + + //get provider + QgsVectorDataProvider *provider = vlayer->dataProvider(); + + // verifying specific capabilities + int cap = provider->capabilities(); + if ( !( cap & QgsVectorDataProvider::AddFeatures ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "No capabilities to do WFS inserts on layer '%1'" ).arg( typeName ); + continue; + } + + // start editing + vlayer->startEditing(); + + // get inserting features + QgsFeatureList featureList; + try + { + featureList = featuresFromGML( action.featureNodeList, provider ); + } + catch ( QgsOgcServiceException &ex ) + { + action.error = true; + action.errorMsg = QStringLiteral( "%1 '%2'" ).arg( ex.message() ).arg( typeName ); + continue; + } + // control features + if ( accessControl ) + { + QgsFeatureList::iterator featureIt = featureList.begin(); + while ( featureIt != featureList.end() ) + { + if ( !accessControl->allowToEdit( vlayer, *featureIt ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Feature modify permission denied on layer '%1'" ).arg( typeName ); + vlayer->rollBack(); + break; } featureIt++; } -#endif - - // add the features - if ( !provider->addFeatures( inFeatList ) ) - { - addTransactionResult( resp, respElem, QStringLiteral( "Partial" ), QStringLiteral( "Insert" ), layer->commitErrors().join( QStringLiteral( "\n " ) ) ); - if ( provider->hasErrors() ) - { - provider->clearErrors(); - } - return resp; - } - // Get the Feature Ids of the inserted feature - for ( int j = 0; j < inFeatList.size(); j++ ) - { - insertResults << typeName + "." + QString::number( inFeatList[j].id() ); - } } - } - - // Put the Feature Ids of the inserted feature - if ( !insertResults.isEmpty() ) - { - Q_FOREACH ( const QString &fidStr, insertResults ) + if ( action.error ) { - QDomElement irElem = doc.createElement( QStringLiteral( "InsertResult" ) ); - QDomElement fiElem = doc.createElement( QStringLiteral( "ogc:FeatureId" ) ); - fiElem.setAttribute( QStringLiteral( "fid" ), fidStr ); - irElem.appendChild( fiElem ); - respElem.appendChild( irElem ); + continue; + } + + // perform add features + if ( !provider->addFeatures( featureList ) ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Insert features failed on layer '%1'" ).arg( typeName ); + if ( provider ->hasErrors() ) + { + provider->clearErrors(); + } + vlayer->rollBack(); + continue; + } + + // Commit the changes of the update elements + if ( !vlayer->commitChanges() ) + { + action.error = true; + action.errorMsg = QStringLiteral( "Error committing inserts: %1" ).arg( vlayer->commitErrors().join( QStringLiteral( "; " ) ) ); + vlayer->rollBack(); + continue; + } + // all changes are OK! + action.error = false; + + // Get the Feature Ids of the inserted feature + for ( int j = 0; j < featureList.size(); j++ ) + { + action.insertFeatureIds << typeName + "." + QString::number( featureList[j].id() ); } } - // Set the transaction reposne for success - QDomElement trElem = doc.createElement( QStringLiteral( "TransactionResult" ) ); - QDomElement stElem = doc.createElement( QStringLiteral( "Status" ) ); - QDomElement successElem = doc.createElement( QStringLiteral( "SUCCESS" ) ); - stElem.appendChild( successElem ); - trElem.appendChild( stElem ); - respElem.appendChild( trElem ); + //force restoration of original layer filters + filterRestorer.reset(); + } - return resp; + QgsFeatureList featuresFromGML( QDomNodeList featureNodeList, QgsVectorDataProvider *provider ) + { + // Store the inserted features + QgsFeatureList featList; + + // Get Layer Field Information + QgsFields fields = provider->fields(); + QMap fieldMap = provider->fieldNameMap(); + QMap::const_iterator fieldMapIt; + + for ( int i = 0; i < featureNodeList.count(); i++ ) + { + QgsFeature feat( fields ); + + QDomElement featureElem = featureNodeList.at( i ).toElement(); + QDomNode currentAttributeChild = featureElem.firstChild(); + bool conversionSuccess = true; + + while ( !currentAttributeChild.isNull() ) + { + QDomElement currentAttributeElement = currentAttributeChild.toElement(); + QString attrName = currentAttributeElement.localName(); + + if ( attrName != QLatin1String( "boundedBy" ) ) + { + if ( attrName != QLatin1String( "geometry" ) ) //a normal attribute + { + fieldMapIt = fieldMap.find( attrName ); + if ( fieldMapIt == fieldMap.constEnd() ) + { + continue; + } + + QgsField field = fields.at( fieldMapIt.value() ); + QString attrValue = currentAttributeElement.text(); + int attrType = field.type(); + + QgsMessageLog::logMessage( QStringLiteral( "attr: name=%1 idx=%2 value=%3" ).arg( attrName ).arg( fieldMapIt.value() ).arg( attrValue ) ); + + if ( attrType == QVariant::Int ) + feat.setAttribute( fieldMapIt.value(), attrValue.toInt( &conversionSuccess ) ); + else if ( attrType == QVariant::Double ) + feat.setAttribute( fieldMapIt.value(), attrValue.toDouble( &conversionSuccess ) ); + else + feat.setAttribute( fieldMapIt.value(), attrValue ); + + if ( !conversionSuccess ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Property conversion error on layer insert" ) ); + } + } + else //a geometry attribute + { + QgsGeometry g = QgsOgcUtils::geometryFromGML( currentAttributeElement ); + if ( g.isNull() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Geometry from GML error on layer insert" ) ); + } + feat.setGeometry( g ); + } + } + currentAttributeChild = currentAttributeChild.nextSibling(); + } + // update feature list + featList << feat; + } + return featList; + } + + transactionRequest parseTransactionParameters( QgsServerRequest::Parameters parameters ) + { + if ( !parameters.contains( QStringLiteral( "OPERATION" ) ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "OPERATION parameter is mandatory" ) ); + } + if ( parameters.value( QStringLiteral( "OPERATION" ) ).toUpper() != QStringLiteral( "DELETE" ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Only DELETE value is defined for OPERATION parameter" ) ); + } + + // Verifying parameters mutually exclusive + if ( ( parameters.contains( QStringLiteral( "FEATUREID" ) ) + && ( parameters.contains( QStringLiteral( "FILTER" ) ) || parameters.contains( QStringLiteral( "BBOX" ) ) ) ) + || ( parameters.contains( QStringLiteral( "FILTER" ) ) + && ( parameters.contains( QStringLiteral( "FEATUREID" ) ) || parameters.contains( QStringLiteral( "BBOX" ) ) ) ) + || ( parameters.contains( QStringLiteral( "BBOX" ) ) + && ( parameters.contains( QStringLiteral( "FEATUREID" ) ) || parameters.contains( QStringLiteral( "FILTER" ) ) ) ) + ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) ); + } + + transactionRequest request; + + QStringList typeNameList; + // parse FEATUREID + if ( parameters.contains( QStringLiteral( "FEATUREID" ) ) ) + { + QStringList fidList = parameters.value( QStringLiteral( "FEATUREID" ) ).split( QStringLiteral( "," ) ); + + QMap fidsMap; + + QStringList::const_iterator fidIt = fidList.constBegin(); + for ( ; fidIt != fidList.constEnd(); ++fidIt ) + { + // Get FeatureID + QString fid = *fidIt; + fid = fid.trimmed(); + // testing typename in the WFS featureID + if ( !fid.contains( QLatin1String( "." ) ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) ); + } + + QString typeName = fid.section( QStringLiteral( "." ), 0, 0 ); + fid = fid.section( QStringLiteral( "." ), 1, 1 ); + if ( !typeNameList.contains( typeName ) ) + { + typeNameList << typeName; + } + + QgsFeatureIds fids; + if ( fidsMap.contains( typeName ) ) + { + fids = fidsMap.value( typeName ); + } + fids.insert( fid.toInt() ); + fidsMap.insert( typeName, fids ); + } + + QMap::const_iterator fidsMapIt = fidsMap.constBegin(); + while ( fidsMapIt != fidsMap.constEnd() ) + { + transactionDelete action; + action.typeName = fidsMapIt.key(); + + QgsFeatureIds fids = fidsMapIt.value(); + action.featureRequest = QgsFeatureRequest( fids ); + + request.deletes.append( action ); + } + return request; + } + + if ( !parameters.contains( QStringLiteral( "TYPENAME" ) ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is mandatory except if FEATUREID is used" ) ); + } + + typeNameList = parameters.value( QStringLiteral( "TYPENAME" ) ).split( QStringLiteral( "," ) ); + + // Create actions based on TypeName + QStringList::const_iterator typeNameIt = typeNameList.constBegin(); + for ( ; typeNameIt != typeNameList.constEnd(); ++typeNameIt ) + { + QString typeName = *typeNameIt; + typeName = typeName.trimmed(); + + transactionDelete action; + action.typeName = typeName; + + request.deletes.append( action ); + } + + // Manage extra parameter exp_filter + if ( parameters.contains( QStringLiteral( "EXP_FILTER" ) ) ) + { + QString expFilterName = parameters.value( QStringLiteral( "EXP_FILTER" ) ); + QStringList expFilterList; + QRegExp rx( "\\(([^()]+)\\)" ); + if ( rx.indexIn( expFilterName, 0 ) == -1 ) + { + expFilterList << expFilterName; + } + else + { + int pos = 0; + while ( ( pos = rx.indexIn( expFilterName, pos ) ) != -1 ) + { + expFilterList << rx.cap( 1 ); + pos += rx.matchedLength(); + } + } + + // Verifying the 1:1 mapping between TYPENAME and EXP_FILTER but without exception + if ( request.deletes.size() == expFilterList.size() ) + { + // set feature request filter expression based on filter element + QList::iterator dIt = request.deletes.begin(); + QStringList::const_iterator expFilterIt = expFilterList.constBegin(); + for ( ; dIt != request.deletes.end(); ++dIt ) + { + transactionDelete &action = *dIt; + // Get Filter for this typeName + QString expFilter; + if ( expFilterIt != expFilterList.constEnd() ) + { + expFilter = *expFilterIt; + } + std::shared_ptr filter( new QgsExpression( expFilter ) ); + if ( filter ) + { + if ( filter->hasParserError() ) + { + QgsMessageLog::logMessage( filter->parserErrorString() ); + } + else + { + if ( filter->needsGeometry() ) + { + action.featureRequest.setFlags( QgsFeatureRequest::NoFlags ); + } + action.featureRequest.setFilterExpression( filter->expression() ); + } + } + } + } + else + { + QgsMessageLog::logMessage( "There has to be a 1:1 mapping between each element in a TYPENAME and the EXP_FILTER list" ); + } + } + + if ( parameters.contains( QStringLiteral( "BBOX" ) ) ) + { + // get bbox value + QString bbox = parameters.value( QStringLiteral( "BBOX" ) ); + if ( bbox.isEmpty() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX parameter is empty" ) ); + } + + // get bbox corners + QStringList corners = bbox.split( "," ); + if ( corners.size() != 4 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX has to be composed of 4 elements: '%1'" ).arg( bbox ) ); + } + + // convert corners to double + double d[4]; + bool ok; + for ( int i = 0; i < 4; i++ ) + { + corners[i].replace( QLatin1String( " " ), QLatin1String( "+" ) ); + d[i] = corners[i].toDouble( &ok ); + if ( !ok ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "BBOX has to be composed of 4 double: '%1'" ).arg( bbox ) ); + } + } + // create extent + QgsRectangle extent( d[0], d[1], d[2], d[3] ); + + // set feature request filter rectangle + QList::iterator dIt = request.deletes.begin(); + for ( ; dIt != request.deletes.end(); ++dIt ) + { + transactionDelete &action = *dIt; + action.featureRequest.setFilterRect( extent ); + } + return request; + } + else if ( parameters.contains( QStringLiteral( "FILTER" ) ) ) + { + QString filterName = parameters.value( QStringLiteral( "FILTER" ) ); + QStringList filterList; + QRegExp rx( "\\(([^()]+)\\)" ); + if ( rx.indexIn( filterName, 0 ) == -1 ) + { + filterList << filterName; + } + else + { + int pos = 0; + while ( ( pos = rx.indexIn( filterName, pos ) ) != -1 ) + { + filterList << rx.cap( 1 ); + pos += rx.matchedLength(); + } + } + + // Verifying the 1:1 mapping between TYPENAME and FILTER + if ( request.deletes.size() != filterList.size() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the FILTER list" ) ); + } + + // set feature request filter expression based on filter element + QList::iterator dIt = request.deletes.begin(); + QStringList::const_iterator filterIt = filterList.constBegin(); + for ( ; dIt != request.deletes.end(); ++dIt ) + { + transactionDelete &action = *dIt; + + // Get Filter for this typeName + QDomDocument filter; + if ( filterIt != filterList.constEnd() ) + { + QString errorMsg; + if ( !filter.setContent( *filterIt, true, &errorMsg ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, *filterIt ) ); + } + } + + QDomElement filterElem = filter.firstChildElement(); + action.featureRequest = parseFilterElement( action.typeName, filterElem ); + + if ( filterIt != filterList.constEnd() ) + { + ++filterIt; + } + } + return request; + } + + return request; + } + + transactionRequest parseTransactionRequestBody( QDomElement &docElem ) + { + transactionRequest request; + + QDomNodeList docChildNodes = docElem.childNodes(); + + QDomElement actionElem; + QString actionName; + + for ( int i = docChildNodes.count(); 0 < i; --i ) + { + actionElem = docChildNodes.at( i - 1 ).toElement(); + actionName = actionElem.localName(); + + if ( actionName == QLatin1String( "Insert" ) ) + { + transactionInsert action = parseInsertActionElement( actionElem ); + request.inserts.append( action ); + } + else if ( actionName == QLatin1String( "Update" ) ) + { + transactionUpdate action = parseUpdateActionElement( actionElem ); + request.updates.append( action ); + } + else if ( actionName == QLatin1String( "Delete" ) ) + { + transactionDelete action = parseDeleteActionElement( actionElem ); + request.deletes.append( action ); + } + } + + return request; + } + + transactionDelete parseDeleteActionElement( QDomElement &actionElem ) + { + QString typeName = actionElem.attribute( QStringLiteral( "typeName" ) ); + if ( typeName.contains( QLatin1String( ":" ) ) ) + typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); + + QDomElement filterElem = actionElem.firstChild().toElement(); + if ( filterElem.tagName() != QLatin1String( "Filter" ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Delete action element first child is not Filter" ) ); + } + + QgsFeatureRequest featureRequest = parseFilterElement( typeName, filterElem ); + + transactionDelete action; + action.typeName = typeName; + action.featureRequest = featureRequest; + action.error = false; + + if ( actionElem.hasAttribute( QStringLiteral( "handle" ) ) ) + { + action.handle = actionElem.attribute( QStringLiteral( "handle" ) ); + } + + return action; + } + + transactionUpdate parseUpdateActionElement( QDomElement &actionElem ) + { + QString typeName = actionElem.attribute( QStringLiteral( "typeName" ) ); + if ( typeName.contains( QLatin1String( ":" ) ) ) + typeName = typeName.section( QStringLiteral( ":" ), 1, 1 ); + + QDomNodeList propertyNodeList = actionElem.elementsByTagName( QStringLiteral( "Property" ) ); + if ( propertyNodeList.size() != 1 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Update action element must have one or more Property element" ) ); + } + + QMap propertyMap; + QDomElement propertyElem; + QDomElement nameElem; + QDomElement valueElem; + QDomElement geometryElem; + + for ( int l = 0; l < propertyNodeList.count(); ++l ) + { + propertyElem = propertyNodeList.at( l ).toElement(); + nameElem = propertyElem.elementsByTagName( QStringLiteral( "Name" ) ).at( 0 ).toElement(); + valueElem = propertyElem.elementsByTagName( QStringLiteral( "Value" ) ).at( 0 ).toElement(); + if ( nameElem.text() != QLatin1String( "geometry" ) ) + { + propertyMap.insert( nameElem.text(), valueElem.text() ); + } + else + { + geometryElem = valueElem; + } + } + + QDomNodeList filterNodeList = actionElem.elementsByTagName( QStringLiteral( "Filter" ) ); + QgsFeatureRequest featureRequest; + if ( filterNodeList.size() != 0 ) + { + QDomElement filterElem = filterNodeList.at( 0 ).toElement(); + featureRequest = parseFilterElement( typeName, filterElem ); + } + + transactionUpdate action; + action.typeName = typeName; + action.propertyMap = propertyMap; + action.geometryElement = geometryElem; + action.featureRequest = featureRequest; + action.error = false; + + if ( actionElem.hasAttribute( QStringLiteral( "handle" ) ) ) + { + action.handle = actionElem.attribute( QStringLiteral( "handle" ) ); + } + + return action; + } + + transactionInsert parseInsertActionElement( QDomElement &actionElem ) + { + QDomNodeList featureNodeList = actionElem.childNodes(); + if ( featureNodeList.size() != 1 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Insert action element must have one or more child node" ) ); + } + + QString typeName; + for ( int i = 0; i < featureNodeList.count(); ++i ) + { + QString tempTypeName = featureNodeList.at( i ).toElement().localName(); + if ( tempTypeName.contains( QLatin1String( ":" ) ) ) + tempTypeName = tempTypeName.section( QStringLiteral( ":" ), 1, 1 ); + + if ( typeName.isEmpty() ) + { + typeName = tempTypeName; + } + else if ( tempTypeName != typeName ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Insert action element must have one typename features" ) ); + } + } + + transactionInsert action; + action.typeName = typeName; + action.featureNodeList = featureNodeList; + action.error = false; + + if ( actionElem.hasAttribute( QStringLiteral( "handle" ) ) ) + { + action.handle = actionElem.attribute( QStringLiteral( "handle" ) ); + } + + return action; } namespace { - void addTransactionResult( QDomDocument &responseDoc, QDomElement &responseElem, const QString &status, const QString &locator, const QString &message ) { @@ -501,74 +1169,23 @@ namespace QgsWfs trElem.appendChild( stElem ); responseElem.appendChild( trElem ); - QDomElement locElem = responseDoc.createElement( QStringLiteral( "Locator" ) ); - locElem.appendChild( responseDoc.createTextNode( locator ) ); - trElem.appendChild( locElem ); - - QDomElement mesElem = responseDoc.createElement( QStringLiteral( "Message" ) ); - mesElem.appendChild( responseDoc.createTextNode( message ) ); - trElem.appendChild( mesElem ); - } - - QgsFeatureIds getFeatureIdsFromFilter( const QDomElement &filterElem, QgsVectorLayer *layer ) - { - QgsFeatureIds fids; - - QgsVectorDataProvider *provider = layer->dataProvider(); - QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) ); - - if ( fidNodes.size() != 0 ) + if ( !locator.isEmpty() ) { - QDomElement fidElem; - QString fid; - bool conversionSuccess; - for ( int i = 0; i < fidNodes.size(); ++i ) - { - fidElem = fidNodes.at( i ).toElement(); - fid = fidElem.attribute( QStringLiteral( "fid" ) ); - if ( fid.contains( QLatin1String( "." ) ) ) - fid = fid.section( QStringLiteral( "." ), 1, 1 ); - fids.insert( fid.toLongLong( &conversionSuccess ) ); - } - } - else - { - std::shared_ptr filter( QgsOgcUtils::expressionFromOgcFilter( filterElem ) ); - if ( filter ) - { - if ( filter->hasParserError() ) - { - throw QgsRequestNotWellFormedException( filter->parserErrorString() ); - } - QgsFeature feature; - QgsFields fields = provider->fields(); - QgsFeatureIterator fit = layer->getFeatures(); - QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( feature, fields ); - - while ( fit.nextFeature( feature ) ) - { - context.setFeature( feature ); - QVariant res = filter->evaluate( &context ); - if ( filter->hasEvalError() ) - { - throw QgsRequestNotWellFormedException( filter->evalErrorString() ); - } - if ( res.toInt() != 0 ) - { - fids.insert( feature.id() ); - } - } - } + QDomElement locElem = responseDoc.createElement( QStringLiteral( "Locator" ) ); + locElem.appendChild( responseDoc.createTextNode( locator ) ); + trElem.appendChild( locElem ); } - return fids; + if ( !message.isEmpty() ) + { + QDomElement mesElem = responseDoc.createElement( QStringLiteral( "Message" ) ); + mesElem.appendChild( responseDoc.createTextNode( message ) ); + trElem.appendChild( mesElem ); + } } - } - - } // samespace QgsWfs diff --git a/src/server/services/wfs/qgswfstransaction.h b/src/server/services/wfs/qgswfstransaction.h index 1655f233940..b787732be0a 100644 --- a/src/server/services/wfs/qgswfstransaction.h +++ b/src/server/services/wfs/qgswfstransaction.h @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler (original code) + (C) 2012 by René-Luc D'Hont (original code) (C) 2014 by Alessandro Pasotti (original code) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -24,19 +25,99 @@ namespace QgsWfs { + struct transactionInsert + { + QString typeName; + + QString handle; + + QDomNodeList featureNodeList; + + QStringList insertFeatureIds; + + bool error; + + QString errorMsg; + }; + + struct transactionUpdate + { + QString typeName; + + QString handle; + + QMap propertyMap; + + QDomElement geometryElement; + + QgsFeatureRequest featureRequest; + + bool error; + + QString errorMsg; + }; + + struct transactionDelete + { + QString typeName; + + QString handle; + + QgsFeatureRequest featureRequest; + + bool error; + + QString errorMsg; + }; + + struct transactionRequest + { + QList< transactionInsert > inserts; + + QList< transactionUpdate > updates; + + QList< transactionDelete > deletes; + }; + + /** Transform Insert element to transactionInsert + */ + transactionInsert parseInsertActionElement( QDomElement &actionElem ); + + /** Transform Update element to transactionUpdate + */ + transactionUpdate parseUpdateActionElement( QDomElement &actionElem ); + + /** Transform Delete element to transactionDelete + */ + transactionDelete parseDeleteActionElement( QDomElement &actionElem ); + + /** Transform RequestBody root element to getFeatureRequest + */ + transactionRequest parseTransactionRequestBody( QDomElement &docElem ); + + transactionRequest parseTransactionParameters( QgsServerRequest::Parameters parameters ); + + /** Transform GML feature nodes to features + */ + QgsFeatureList featuresFromGML( QDomNodeList featureNodeList, QgsVectorDataProvider *provider ); + + /** Perform the transaction + */ + void performTransaction( transactionRequest &aRequest, QgsServerInterface *serverIface, const QgsProject *project ); /** * Output WFS transaction response */ - void writeTransaction( QgsServerInterface *serverIface, const QString &version, - const QgsServerRequest &request, QgsServerResponse &response ); + void writeTransaction( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ); /** * Create a wfs transaction document */ - QDomDocument createTransactionDocument( QgsServerInterface *serverIface, const QString &version, - const QgsServerRequest &request ); + QDomDocument createTransactionDocument( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request ); } // samespace QgsWfs diff --git a/src/server/services/wfs/qgswfsutils.cpp b/src/server/services/wfs/qgswfsutils.cpp index c0672435fa7..dd2111bf0e2 100644 --- a/src/server/services/wfs/qgswfsutils.cpp +++ b/src/server/services/wfs/qgswfsutils.cpp @@ -3,6 +3,7 @@ ------------------------- begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler ( parts fron qgswmshandler) + (C) 2012 by René-Luc D'Hont ( parts from qgswmshandler) (C) 2014 by Alessandro Pasotti ( parts from qgswmshandler) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -20,6 +21,7 @@ ***************************************************************************/ #include "qgswfsutils.h" +#include "qgsogcutils.h" #include "qgsconfigcache.h" #include "qgsserverprojectutils.h" @@ -30,21 +32,6 @@ namespace QgsWfs return QStringLiteral( "1.0.0" ); } - // Return the wms config parser (Transitional) - QgsWfsProjectParser *getConfigParser( QgsServerInterface *serverIface ) - { - QString configFilePath = serverIface->configFilePath(); - - QgsWfsProjectParser *parser = QgsConfigCache::instance()->wfsConfiguration( configFilePath, serverIface->accessControls() ); - if ( !parser ) - { - throw QgsServiceException( - QStringLiteral( "WFS configuration error" ), - QStringLiteral( "There was an error reading the project file or the SLD configuration" ) ); - } - return parser; - } - QString serviceUrl( const QgsServerRequest &request, const QgsProject *project ) { QString href; @@ -71,6 +58,86 @@ namespace QgsWfs return href; } + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem ) + { + QgsFeatureRequest request; + + QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) ); + if ( !fidNodes.isEmpty() ) + { + QgsFeatureIds fids; + QDomElement fidElem; + for ( int f = 0; f < fidNodes.size(); f++ ) + { + fidElem = fidNodes.at( f ).toElement(); + if ( !fidElem.hasAttribute( QStringLiteral( "fid" ) ) ) + { + throw QgsRequestNotWellFormedException( "FeatureId element without fid attribute" ); + } + + QString fid = fidElem.attribute( QStringLiteral( "fid" ) ); + if ( fid.contains( QLatin1String( "." ) ) ) + { + if ( fid.section( QStringLiteral( "." ), 0, 0 ) != typeName ) + continue; + fid = fid.section( QStringLiteral( "." ), 1, 1 ); + } + fids.insert( fid.toInt() ); + } + + if ( fids.size() > 0 ) + { + request.setFilterFids( fids ); + } + else + { + throw QgsRequestNotWellFormedException( QStringLiteral( "No FeatureId element corrcetly parse against typeName '%1'" ).arg( typeName ) ); + } + request.setFlags( QgsFeatureRequest::NoFlags ); + return request; + } + else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) ) + { + QDomElement bboxElem = filterElem.firstChildElement(); + QDomElement childElem = bboxElem.firstChildElement(); + + while ( !childElem.isNull() ) + { + if ( childElem.tagName() == QLatin1String( "Box" ) ) + { + request.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) ); + } + else if ( childElem.tagName() != QLatin1String( "PropertyName" ) ) + { + QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem ); + request.setFilterRect( geom.boundingBox() ); + } + childElem = childElem.nextSiblingElement(); + } + request.setFlags( QgsFeatureRequest::ExactIntersect | QgsFeatureRequest::NoFlags ); + return request; + } + else + { + std::shared_ptr filter( QgsOgcUtils::expressionFromOgcFilter( filterElem ) ); + if ( filter ) + { + if ( filter->hasParserError() ) + { + throw QgsRequestNotWellFormedException( filter->parserErrorString() ); + } + + if ( filter->needsGeometry() ) + { + request.setFlags( QgsFeatureRequest::NoFlags ); + } + request.setFilterExpression( filter->expression() ); + return request; + } + } + return request; + } + } // namespace QgsWfs diff --git a/src/server/services/wfs/qgswfsutils.h b/src/server/services/wfs/qgswfsutils.h index 9bfd6c3cb47..f07b82b5a2a 100644 --- a/src/server/services/wfs/qgswfsutils.h +++ b/src/server/services/wfs/qgswfsutils.h @@ -5,6 +5,7 @@ ------------------------------------ begin : December 20 , 2016 copyright : (C) 2007 by Marco Hugentobler ( parts fron qgswfshandler) + (C) 2012 by René-Luc D'Hont ( parts from qgswmshandler) (C) 2014 by Alessandro Pasotti ( parts from qgswfshandler) (C) 2017 by David Marteau email : marco dot hugentobler at karto dot baug dot ethz dot ch @@ -24,7 +25,7 @@ #define QGSWFSUTILS_H #include "qgsmodule.h" -#include "qgswfsprojectparser.h" +#include "qgsfeaturerequest.h" #include "qgswfsserviceexception.h" /** @@ -40,19 +41,15 @@ namespace QgsWfs */ QString implementationVersion(); - /** - * Return the wms config parser (Transitional) - * - * XXX This is needed in the current implementation. - * This should disappear as soon we get rid of singleton. - */ - QgsWfsProjectParser *getConfigParser( QgsServerInterface *serverIface ); - /** * Service URL string */ QString serviceUrl( const QgsServerRequest &request, const QgsProject *project ); + /** Transform a Filter element to a feature request + */ + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem ); + // Define namespaces used in WFS documents const QString WFS_NAMESPACE = QStringLiteral( "http://www.opengis.net/wfs" ); const QString GML_NAMESPACE = QStringLiteral( "http://www.opengis.net/gml" ); diff --git a/tests/src/python/test_qgsserver_accesscontrol.py b/tests/src/python/test_qgsserver_accesscontrol.py index 4269f6ac4eb..7b4364b783b 100644 --- a/tests/src/python/test_qgsserver_accesscontrol.py +++ b/tests/src/python/test_qgsserver_accesscontrol.py @@ -458,7 +458,7 @@ class TestQgsServerAccessControl(unittest.TestCase): query_string = "&".join(["%s=%s" % i for i in list({ "MAP": urllib.parse.quote(self.projectPath), "SERVICE": "WFS", - "VERSION": "1.1.0", + "VERSION": "1.0.0", "REQUEST": "GetCapabilities" }.items())]) @@ -482,7 +482,7 @@ class TestQgsServerAccessControl(unittest.TestCase): query_string = "&".join(["%s=%s" % i for i in list({ "MAP": urllib.parse.quote(self.projectPath), "SERVICE": "WFS", - "VERSION": "1.1.0", + "VERSION": "1.0.0", "REQUEST": "DescribeFeatureType", "TYPENAME": "Hello" }.items())]) @@ -501,7 +501,7 @@ class TestQgsServerAccessControl(unittest.TestCase): query_string = "&".join(["%s=%s" % i for i in list({ "MAP": urllib.parse.quote(self.projectPath), "SERVICE": "WFS", - "VERSION": "1.1.0", + "VERSION": "1.0.0", "REQUEST": "DescribeFeatureType", "TYPENAME": "Country" }.items())]) @@ -722,8 +722,7 @@ class TestQgsServerAccessControl(unittest.TestCase): headers.get("Content-Type"), "text/xml; charset=utf-8", "Content type for Insert is wrong: %s" % headers.get("Content-Type")) self.assertTrue( - str(response).find( - 'Feature modify permission denied') != -1, + str(response).find("") == -1, "WFS/Transactions Insert succeed\n%s" % response) response, headers = self._post_restricted(data.format(color="red"), "LAYER_PERM=no") @@ -732,7 +731,7 @@ class TestQgsServerAccessControl(unittest.TestCase): "Content type for Insert is wrong: %s" % headers.get("Content-Type")) self.assertTrue( str(response).find( - 'Feature insert permission denied') != -1, + 'No permissions to do WFS changes on layer \\\'db_point\\\'') != -1, "WFS/Transactions Insert succeed\n%s" % response) response, headers = self._post_restricted(data.format(color="yellow"), "LAYER_PERM=yes") @@ -753,8 +752,7 @@ class TestQgsServerAccessControl(unittest.TestCase): headers.get("Content-Type"), "text/xml; charset=utf-8", "Content type for GetMap is wrong: %s" % headers.get("Content-Type")) self.assertTrue( - str(response).find( - 'Feature modify permission denied') != -1, + str(response).find("") == -1, "WFS/Transactions Update succeed\n%s" % response) self._test_colors({1: "blue"}) @@ -772,8 +770,7 @@ class TestQgsServerAccessControl(unittest.TestCase): headers.get("Content-Type"), "text/xml; charset=utf-8", "Content type for Update is wrong: %s" % headers.get("Content-Type")) self.assertTrue( - str(response).find( - 'Feature modify permission denied') != -1, + str(response).find("") == -1, "WFS/Transactions Update succeed\n%s" % response) self._test_colors({1: "red"}) @@ -783,7 +780,7 @@ class TestQgsServerAccessControl(unittest.TestCase): "Content type for Update is wrong: %s" % headers.get("Content-Type")) self.assertTrue( str(response).find( - 'Feature update permission denied') != -1, + 'No permissions to do WFS changes on layer \\\'db_point\\\'') != -1, "WFS/Transactions Update succeed\n%s" % response) self._test_colors({1: "red"}) @@ -817,8 +814,7 @@ class TestQgsServerAccessControl(unittest.TestCase): headers.get("Content-Type"), "text/xml; charset=utf-8", "Content type for GetMap is wrong: %s" % headers.get("Content-Type")) self.assertTrue( - str(response).find( - 'Feature modify permission denied') != -1, + str(response).find("") == -1, "WFS/Transactions Delete succeed\n%s" % response) data_update = WFS_TRANSACTION_UPDATE.format(id="1", color="red", xml_ns=XML_NS) @@ -831,7 +827,7 @@ class TestQgsServerAccessControl(unittest.TestCase): "Content type for GetMap is wrong: %s" % headers.get("Content-Type")) self.assertTrue( str(response).find( - 'Feature delete permission denied') != -1, + 'No permissions to do WFS changes on layer \\\'db_point\\\'') != -1, "WFS/Transactions Delete succeed\n%s" % response) response, headers = self._post_restricted(data, "LAYER_PERM=yes") diff --git a/tests/src/python/test_qgsserver_wfst.py b/tests/src/python/test_qgsserver_wfst.py index 8272521e579..37f1268ecdc 100644 --- a/tests/src/python/test_qgsserver_wfst.py +++ b/tests/src/python/test_qgsserver_wfst.py @@ -183,6 +183,7 @@ class TestWFST(unittest.TestCase): layer = self._getLayer(layer.name()) self.assertTrue(layer.isValid()) self.assertEqual(layer.featureCount(), len(features)) + self.assertEqual(wfs_layer.dataProvider().featureCount(), len(features)) def _checkUpdateFeatures(self, wfs_layer, old_features, new_features): """ diff --git a/tests/testdata/qgis_server/wfs_getcapabilities.txt b/tests/testdata/qgis_server/wfs_getcapabilities.txt index 420df606b63..5c8c1dea27e 100644 --- a/tests/testdata/qgis_server/wfs_getcapabilities.txt +++ b/tests/testdata/qgis_server/wfs_getcapabilities.txt @@ -6,21 +6,19 @@ Content-Type: text/xml; charset=utf-8 WFS QGIS TestProject Some UTF8 text èòù - - None - None + - + - + @@ -30,12 +28,12 @@ Content-Type: text/xml; charset=utf-8 - + - + @@ -47,19 +45,19 @@ Content-Type: text/xml; charset=utf-8 - + - + - + @@ -74,13 +72,13 @@ Content-Type: text/xml; charset=utf-8 A test vector layer A test vector layer with unicode òà EPSG:4326 + -