Skip to content

Commit ccb4c80

Browse files
committedJan 23, 2018
[bugfix][wfs] Expand support for 2.0.0 TYPENAMES
Fixes #17872 - WFS 2.0.0 DescribeFeatureType : TypeNames vs TypeName This PR introduces the plural form of TYPENAME for 2.0.0 WFS servers, with some additional logic to allow for 2.0.0 servers that only support TYPENAME for DescribeFeatureType: in this case, the singular form is also tried in case the plural one fails. There is still some work to do for transactional support but at least the read-only client part should now be ok. Tests have been added to check that: - TYPENAME form still works with old 2.0.0 - TYPENAMES form works with compliant 2.0.0 - choice geometry types are handled by calling GetFeature and examining the result
1 parent 3b0afb2 commit ccb4c80

File tree

9 files changed

+424
-35
lines changed

9 files changed

+424
-35
lines changed
 

‎src/providers/wfs/qgswfsconnection.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName )
2828
const QString &version = settings.value( key + "/" + QgsWFSConstants::SETTINGS_VERSION ).toString();
2929
if ( !version.isEmpty() )
3030
{
31+
mUri.removeParam( QgsWFSConstants::URI_PARAM_VERSION ); // setParam allow for duplicates!
3132
mUri.setParam( QgsWFSConstants::URI_PARAM_VERSION, version );
3233
}
3334

3435
const QString &maxnumfeatures = settings.value( key + "/" + QgsWFSConstants::SETTINGS_MAXNUMFEATURES ).toString();
3536
if ( !maxnumfeatures.isEmpty() )
3637
{
38+
mUri.removeParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES ); // setParam allow for duplicates!
3739
mUri.setParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES, maxnumfeatures );
3840
}
3941

‎src/providers/wfs/qgswfsdescribefeaturetype.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,25 @@
1414
***************************************************************************/
1515

1616
#include "qgswfsdescribefeaturetype.h"
17+
#include "qgswfsutils.h"
1718

1819
QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( const QString &uri )
1920
: QgsWfsRequest( uri )
2021
{
2122
}
2223

2324
bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion,
24-
const QString &typeName )
25+
const QString &typeName, bool forceSingularTypeName )
2526
{
2627
QUrl url( baseURL() );
2728
url.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
2829
url.addQueryItem( QStringLiteral( "VERSION" ), WFSVersion );
29-
url.addQueryItem( QStringLiteral( "TYPENAME" ), typeName );
30-
30+
// The specs are not consistent: is it singular in 1.0.x and plural in 2.0.0?
31+
// see http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#147
32+
if ( ! forceSingularTypeName )
33+
url.addQueryItem( QgsWFSUtils::typeNameParameterForVersion( WFSVersion ).toUpper( ), typeName );
34+
else
35+
url.addQueryItem( QStringLiteral( "TYPENAME" ), typeName );
3136
return sendGET( url, true, false );
3237
}
3338

‎src/providers/wfs/qgswfsdescribefeaturetype.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class QgsWFSDescribeFeatureType : public QgsWfsRequest
2525
explicit QgsWFSDescribeFeatureType( const QString &uri );
2626

2727
//! Issue the request
28-
bool requestFeatureType( const QString &WFSVersion, const QString &typeName );
28+
bool requestFeatureType( const QString &WFSVersion, const QString &typeName,
29+
bool forceSingularTypeName = false );
2930

3031
protected:
3132
QString errorMessageWithReason( const QString &reason ) override;

‎src/providers/wfs/qgswfsprovider.cpp

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ bool QgsWFSProvider::deleteFeatures( const QgsFeatureIds &id )
919919
transactionDoc.appendChild( transactionElem );
920920
//delete element
921921
QDomElement deleteElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Delete" ) );
922-
deleteElem.setAttribute( QStringLiteral( "typeName" ), tname );
922+
deleteElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname );
923923
QDomElement filterElem = transactionDoc.createElementNS( QgsWFSConstants::OGC_NAMESPACE, QStringLiteral( "Filter" ) );
924924

925925

@@ -984,7 +984,7 @@ bool QgsWFSProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
984984
continue;
985985
}
986986
QDomElement updateElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Update" ) );
987-
updateElem.setAttribute( QStringLiteral( "typeName" ), tname );
987+
updateElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname );
988988
//Property
989989
QDomElement propertyElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Property" ) );
990990
QDomElement nameElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Name" ) );
@@ -1040,6 +1040,7 @@ QString QgsWFSProvider::convertToXML( const QVariant &value )
10401040
return valueStr;
10411041
}
10421042

1043+
10431044
bool QgsWFSProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
10441045
{
10451046
//find out typename from uri and strip namespace prefix
@@ -1065,7 +1066,7 @@ bool QgsWFSProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
10651066
}
10661067

10671068
QDomElement updateElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Update" ) );
1068-
updateElem.setAttribute( QStringLiteral( "typeName" ), tname );
1069+
updateElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname );
10691070

10701071
QgsAttributeMap::const_iterator attMapIt = attIt.value().constBegin();
10711072
for ( ; attMapIt != attIt.value().constEnd(); ++attMapIt )
@@ -1158,17 +1159,20 @@ QString QgsWFSProvider::translateMetadataValue( const QString &mdKey, const QVar
11581159
{
11591160
return value.toString();
11601161
}
1161-
};
1162+
}
11621163

1163-
bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields &fields, QgsWkbTypes::Type &geomType )
1164+
bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute,
1165+
QgsFields &fields,
1166+
QgsWkbTypes::Type &geomType,
1167+
bool forceSingularTypeNames )
11641168
{
11651169
fields.clear();
11661170

11671171
QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI.uri() );
11681172
if ( !describeFeatureType.requestFeatureType( mShared->mWFSVersion,
1169-
mShared->mURI.typeName() ) )
1173+
mShared->mURI.typeName(), forceSingularTypeNames ) )
11701174
{
1171-
QgsMessageLog::logMessage( tr( "DescribeFeatureType failed for url %1: %2" ).
1175+
QgsMessageLog::logMessage( tr( "DescribeFeatureType network request failed for url %1: %2" ).
11721176
arg( dataSourceUri(), describeFeatureType.errorMessage() ), tr( "WFS" ) );
11731177
return false;
11741178
}
@@ -1180,7 +1184,7 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields
11801184
if ( !describeFeatureDocument.setContent( response, true, &errorMsg ) )
11811185
{
11821186
QgsDebugMsg( response );
1183-
QgsMessageLog::logMessage( tr( "DescribeFeatureType failed for url %1: %2" ).
1187+
QgsMessageLog::logMessage( tr( "DescribeFeatureType XML parse failed for url %1: %2" ).
11841188
arg( dataSourceUri(), errorMsg ), tr( "WFS" ) );
11851189
return false;
11861190
}
@@ -1189,11 +1193,19 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields
11891193
mShared->mURI.typeName(),
11901194
geometryAttribute, fields, geomType, errorMsg ) )
11911195
{
1196+
// If 2.0.0, let's assume it was a server that only accepted TYPENAME singular form
1197+
// and try with that ...
1198+
if ( ! forceSingularTypeNames && mShared->mWFSVersion.startsWith( '2' ) )
1199+
{
1200+
return QgsWFSProvider::describeFeatureType( geometryAttribute,
1201+
fields,
1202+
geomType,
1203+
true );
1204+
}
11921205
QgsMessageLog::logMessage( tr( "Analysis of DescribeFeatureType response failed for url %1: %2" ).
11931206
arg( dataSourceUri(), errorMsg ), tr( "WFS" ) );
11941207
return false;
11951208
}
1196-
11971209
return true;
11981210
}
11991211

@@ -1241,6 +1253,20 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
12411253
}
12421254
elementElement = elementElement.nextSiblingElement( QStringLiteral( "element" ) );
12431255
}
1256+
// Try to get a complex type whose name contains the unprefixed typename
1257+
if ( elementTypeString.isEmpty() && complexTypeElement.isNull() )
1258+
{
1259+
const QDomNodeList complexElements = schemaElement.elementsByTagName( QStringLiteral( "complexType" ) );
1260+
for ( int i = 0; i < complexElements.size(); i++ )
1261+
{
1262+
if ( complexElements.at( i ).toElement().attribute( QStringLiteral( "name" ) ).contains( unprefixedTypename ) )
1263+
{
1264+
complexTypeElement = complexElements.at( i ).toElement();
1265+
break;
1266+
}
1267+
}
1268+
}
1269+
// Give up :(
12441270
if ( elementTypeString.isEmpty() && complexTypeElement.isNull() )
12451271
{
12461272
// "http://demo.deegree.org/inspire-workspace/services/wfs?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=ad:Address"
@@ -1266,7 +1292,6 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
12661292
{
12671293
errorMsg = tr( "Cannot find element '%1'" ).arg( unprefixedTypename );
12681294
}
1269-
12701295
return false;
12711296
}
12721297

@@ -1337,29 +1362,33 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc,
13371362
QRegExp gmlRefProperty( "gml:(.*)Property" );
13381363

13391364
// gmgml: is Geomedia Web Server
1340-
if ( type == QLatin1String( "gmgml:Polygon_Surface_MultiSurface_CompositeSurfacePropertyType" ) )
1365+
if ( ! foundGeometryAttribute && type == QLatin1String( "gmgml:Polygon_Surface_MultiSurface_CompositeSurfacePropertyType" ) )
13411366
{
13421367
foundGeometryAttribute = true;
13431368
geometryAttribute = name;
13441369
geomType = QgsWkbTypes::MultiPolygon;
13451370
}
1346-
else if ( type == QLatin1String( "gmgml:LineString_Curve_MultiCurve_CompositeCurvePropertyType" ) )
1371+
else if ( ! foundGeometryAttribute && type == QLatin1String( "gmgml:LineString_Curve_MultiCurve_CompositeCurvePropertyType" ) )
13471372
{
13481373
foundGeometryAttribute = true;
13491374
geometryAttribute = name;
13501375
geomType = QgsWkbTypes::MultiLineString;
13511376
}
13521377
//is it a geometry attribute?
13531378
// the GeometryAssociationType has been seen in #11785
1354-
else if ( type.indexOf( gmlPT ) == 0 || type == QLatin1String( "gml:GeometryAssociationType" ) )
1379+
else if ( ! foundGeometryAttribute && ( type.indexOf( gmlPT ) == 0 || type == QLatin1String( "gml:GeometryAssociationType" ) ) )
13551380
{
13561381
foundGeometryAttribute = true;
13571382
geometryAttribute = name;
1358-
geomType = geomTypeFromPropertyType( geometryAttribute, gmlPT.cap( 1 ) );
1383+
// We have a choice parent element we cannot assume any valid information over the geometry type
1384+
if ( attributeElement.parentNode().nodeName() == QStringLiteral( "choice" ) && ! attributeElement.nextSibling().isNull() )
1385+
geomType = QgsWkbTypes::Unknown;
1386+
else
1387+
geomType = geomTypeFromPropertyType( geometryAttribute, gmlPT.cap( 1 ) );
13591388
}
13601389
//MH 090428: sometimes the <element> tags for geometry attributes have only attribute ref="gml:polygonProperty"
13611390
//Note: this was deprecated with GML3.
1362-
else if ( ref.indexOf( gmlRefProperty ) == 0 )
1391+
else if ( ! foundGeometryAttribute && ref.indexOf( gmlRefProperty ) == 0 )
13631392
{
13641393
foundGeometryAttribute = true;
13651394
geometryAttribute = ref.mid( 4 ); // Strip gml: prefix
@@ -1441,7 +1470,8 @@ QDomElement QgsWFSProvider::createTransactionElement( QDomDocument &doc ) const
14411470
describeFeatureTypeURL = QUrl( QStringLiteral( "http://fake_qgis_http_endpoint" ) );
14421471
describeFeatureTypeURL.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
14431472
describeFeatureTypeURL.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1444-
describeFeatureTypeURL.addQueryItem( QStringLiteral( "TYPENAME" ), mShared->mURI.typeName() );
1473+
//TODO: proper support of 2.0.0, for now hardcoded
1474+
describeFeatureTypeURL.addQueryItem( QgsWFSUtils::typeNameParameterForVersion( WfsVersion ).toUpper(), mShared->mURI.typeName() );
14451475

14461476
transactionElem.setAttribute( QStringLiteral( "xsi:schemaLocation" ), mApplicationNamespace + ' '
14471477
+ describeFeatureTypeURL.toEncoded() );

‎src/providers/wfs/qgswfsprovider.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class QgsWFSProvider : public QgsVectorDataProvider
151151
The method gives back the name of
152152
the geometry attribute and the thematic attributes with their types*/
153153
bool describeFeatureType( QString &geometryAttribute,
154-
QgsFields &fields, QgsWkbTypes::Type &geomType );
154+
QgsFields &fields, QgsWkbTypes::Type &geomType, bool forceSingularTypeNames = false );
155155

156156
/**
157157
* For a given typename, reads the name of the geometry attribute, the

‎src/providers/wfs/qgswfsrequest.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ bool QgsWfsRequest::sendGET( const QUrl &url, bool synchronous, bool forceRefres
9696
}
9797
#endif
9898
modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
99-
QgsDebugMsg( QString( "Get %1 (after laundering)" ).arg( modifiedUrlString ) );
99+
QgsDebugMsg( QStringLiteral( "Get %1 (after laundering)" ).arg( modifiedUrlString ) );
100100
modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
101101
}
102102

103+
QgsDebugMsgLevel( QStringLiteral( "Calling: %1" ).arg( modifiedUrl.toDisplayString( ) ), 4 );
104+
103105
QNetworkRequest request( modifiedUrl );
104106
if ( !mUri.auth().setAuthorization( request ) )
105107
{

‎src/providers/wfs/qgswfsutils.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ bool QgsWFSUtils::removeDir( const QString &dirName )
138138
return dir.rmdir( dirName );
139139
}
140140

141+
QString QgsWFSUtils::typeNameParameterForVersion( const QString &WfsVersion )
142+
{
143+
// WFS 2.0 uses the plural form TYPENAMES
144+
if ( WfsVersion.startsWith( '2' ) )
145+
{
146+
return QString( "typeNames" );
147+
}
148+
else
149+
{
150+
return QString( "typeName" );
151+
}
152+
}
153+
141154

142155
// We use a keep alive mechanism where every KEEP_ALIVE_DELAY ms we update
143156
// a shared memory segment with the current timestamp. This way, other QGIS

‎src/providers/wfs/qgswfsutils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class QgsWFSUtils
4545
//! Return a unique identifier made from feature content
4646
static QString getMD5( const QgsFeature &f );
4747

48+
//! Return the correct form of typeName(s) according to the specified \a WfsVersion
49+
static QString typeNameParameterForVersion( const QString &WfsVersion );
50+
4851
protected:
4952
friend class QgsWFSUtilsKeepAlive;
5053
static QSharedMemory *createAndAttachSHM();
@@ -62,6 +65,7 @@ class QgsWFSUtils
6265

6366
//! Remove (recursively) a directory.
6467
static bool removeDir( const QString &dirName );
68+
6569
};
6670

6771
//! For internal use of QgsWFSUtils

‎tests/src/python/test_provider_wfs.py

Lines changed: 345 additions & 13 deletions
Large diffs are not rendered by default.

6 commit comments

Comments
 (6)

palmerj commented on Apr 30, 2018

@palmerj
Contributor

@elpaso Did you test this with geoserver instances? See https://issues.qgis.org/issues/18882

elpaso commented on Apr 30, 2018

@elpaso
ContributorAuthor

Yes: I think I did.

palmerj commented on Apr 30, 2018

@palmerj
Contributor

Seems that on the Geoserver instances we are running TYPENAMES doesn't work for the DescribeFeatureType request. Instead it just ignores TYPENAMES and returns schemas for the entire catalogue. Our catalogue is very big.

@rouault Do you have any thoughts about this?

rouault commented on Apr 30, 2018

@rouault
Contributor

I don't understand the commit message "TYPENAME form still works with old 2.0.0". From what I see in http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#142 , TYPENAME is the official parameter name for DescribeFeatureType. So servers requiring TYPENAMES are "buggy" (even if the OGC folks got crazy in changing in GetFeatureType from TYPENAME to TYPENAMES and not doing the same for DescribeFeatureType)

So the logic should be trying first TYPENAME and then TYPENAMES. But as @palmerj underlines a server that ignores a parameter could send the whole catalog. So basically there's no good solution except asking servers to fix their implementation to accept TYPENAME. Or providing a manual switch that knowleadgable users could enable.

palmerj commented on Apr 30, 2018

@palmerj
Contributor

Thanks @rouault thanks great information.

So we need to revert this change, then possibly implementing a new advanced switch that provides an override for buggy servers.

palmerj commented on Apr 30, 2018

@palmerj
Contributor

@elpaso are you able to comment?

Please sign in to comment.