Skip to content

Commit

Permalink
[WFS provider] Fix recovery of GeoServer server error when lack of pr…
Browse files Browse the repository at this point in the history
…imary key (fixes #29844)
  • Loading branch information
rouault authored and nyalldawson committed Sep 20, 2019
1 parent 0051cd9 commit 5c8dd17
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 8 deletions.
28 changes: 20 additions & 8 deletions src/providers/wfs/qgswfsfeatureiterator.cpp
Expand Up @@ -548,11 +548,6 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
success = false;
break;
}
if ( mErrorCode != NoError )
{
success = false;
break;
}

QByteArray data;
bool finished = false;
Expand All @@ -573,12 +568,22 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
if ( !parser->processData( data, finished, gmlProcessErrorMsg ) )
{
success = false;
mErrorMessage = tr( "Error when parsing GetFeature response" ) + " : " + gmlProcessErrorMsg;
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
// Only add an error message if no general networking related error has been
// previously reported by QgsWfsRequest logic.
// We indeed make processData() run even if an error has been reported,
// so that we have a chance to parse XML errors (isException() case below)
if ( mErrorCode == NoError )
{
mErrorMessage = tr( "Error when parsing GetFeature response" ) + " : " + gmlProcessErrorMsg;
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
}
break;
}
if ( parser->isException() && finished )
else if ( parser->isException() )
{
// Only process the exception report if we get the full error response.
if ( !finished )
continue;
success = false;

// Some GeoServer instances in WFS 2.0 with paging throw an exception
Expand All @@ -589,6 +594,7 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
{
QgsDebugMsg( QStringLiteral( "Got exception %1. Re-trying with paging disabled" ).arg( parser->exceptionText() ) );
mPageSize = 0;
mShared->mPageSize = 0;
}
// GeoServer doesn't like typenames prefixed by namespace prefix, despite
// the examples in the WFS 2.0 spec showing that
Expand All @@ -604,6 +610,12 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
}
break;
}
// Test error code only after having let a chance to the parser to process the ExceptionReport
else if ( mErrorCode != NoError )
{
success = false;
break;
}

// Consider if we should display a progress dialog
// We can only do that if we know how many features will be downloaded
Expand Down
88 changes: 88 additions & 0 deletions tests/src/python/test_provider_wfs.py
Expand Up @@ -4062,6 +4062,94 @@ def testWFSFieldWithSameNameButDifferentCase(self):
values = [f['FOO'] for f in vl.getFeatures(request)]
self.assertEqual(values, [2])

def testRetryLogicOnExceptionLackOfPrimaryKey(self):
"""Test retry logic on 'Cannot do natural order without a primary key' server exception (GeoServer) """

endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_retry'

with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetCapabilities&VERSION=2.0.0'), 'wb') as f:
f.write("""
<wfs:WFS_Capabilities version="2.0.0" xmlns="http://www.opengis.net/wfs/2.0" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:gml="http://schemas.opengis.net/gml/3.2" xmlns:fes="http://www.opengis.net/fes/2.0">
<OperationsMetadata>
<Operation name="GetFeature">
<Constraint name="CountDefault">
<NoValues/>
<DefaultValue>1</DefaultValue>
</Constraint>
</Operation>
<Constraint name="ImplementsResultPaging">
<NoValues/>
<DefaultValue>TRUE</DefaultValue>
</Constraint>
</OperationsMetadata>
<FeatureTypeList>
<FeatureType>
<Name>my:typename</Name>
<Title>Title</Title>
<Abstract>Abstract</Abstract>
<DefaultCRS>urn:ogc:def:crs:EPSG::4326</DefaultCRS>
<WGS84BoundingBox>
<LowerCorner>-71.123 66.33</LowerCorner>
<UpperCorner>-65.32 78.3</UpperCorner>
</WGS84BoundingBox>
</FeatureType>
</FeatureTypeList>
</wfs:WFS_Capabilities>""".encode('UTF-8'))

with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f:
f.write("""
<xsd:schema xmlns:my="http://my" xmlns:gml="http://www.opengis.net/gml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://my">
<xsd:import namespace="http://www.opengis.net/gml"/>
<xsd:complexType name="typenameType">
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="0" name="INTFIELD" nillable="true" type="xsd:int"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="typename" substitutionGroup="gml:_Feature" type="my:typenameType"/>
</xsd:schema>
""".encode('UTF-8'))

vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='2.0.0'", 'test', 'WFS')
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.NoGeometry)
self.assertEqual(len(vl.fields()), 1)

# Initial request: exception
with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f:
f.write("""<?xml version="1.0" encoding="UTF-8"?><ows:ExceptionReport xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0.0" xsi:schemaLocation="http://www.opengis.net/ows/1.1 http://localhost/geoserver/schemas/ows/1.1.0/owsAll.xsd">
<ows:Exception exceptionCode="NoApplicableCode">
<ows:ExceptionText>java.lang.RuntimeException: java.lang.RuntimeException: java.io.IOException
java.lang.RuntimeException: java.io.IOException
java.io.IOExceptionCannot do natural order without a primary key, please add it or specify a manual sort over existing attributes</ows:ExceptionText>
</ows:Exception>
</ows:ExceptionReport>""".encode('UTF-8'))

# Retry
with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&RETRY=1'), 'wb') as f:
f.write("""
<wfs:FeatureCollection
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:gml="http://www.opengis.net/gml"
xmlns:my="http://my">
<gml:featureMember>
<my:typename fid="typename.0">
<my:INTFIELD>1</my:INTFIELD>
</my:typename>
</gml:featureMember>
<gml:featureMember>
<my:typename fid="typename.1">
<my:INTFIELD>2</my:INTFIELD>
</my:typename>
</gml:featureMember>
</wfs:FeatureCollection>""".encode('UTF-8'))

values = [f['INTFIELD'] for f in vl.getFeatures()]
self.assertEqual(values, [1, 2])


if __name__ == '__main__':
unittest.main()

0 comments on commit 5c8dd17

Please sign in to comment.