Skip to content

Commit

Permalink
[bugfix] Support Distributed Computing Platform (DCP) for WFS
Browse files Browse the repository at this point in the history
Implement the possibility to specify different endpoints
for different WFS operations.

With tests.

Fixes #18099 WFS Capabilities handling problem
  • Loading branch information
elpaso committed Feb 21, 2018
1 parent 42908eb commit f1b5987
Show file tree
Hide file tree
Showing 14 changed files with 542 additions and 190 deletions.
27 changes: 24 additions & 3 deletions src/providers/wfs/qgswfscapabilities.cpp
Expand Up @@ -28,14 +28,14 @@
#include <QStringList>

QgsWfsCapabilities::QgsWfsCapabilities( const QString &uri )
: QgsWfsRequest( uri )
: QgsWfsRequest( QgsWFSDataSourceURI( uri ) )
{
connect( this, &QgsWfsRequest::downloadFinished, this, &QgsWfsCapabilities::capabilitiesReplyFinished );
}

bool QgsWfsCapabilities::requestCapabilities( bool synchronous, bool forceRefresh )
{
QUrl url( baseURL() );
QUrl url( mUri.baseURL( ) );
url.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetCapabilities" ) );

const QString &version = mUri.version();
Expand Down Expand Up @@ -249,7 +249,28 @@ void QgsWfsCapabilities::capabilitiesReplyFinished()
for ( int i = 0; i < operationList.size(); ++i )
{
QDomElement operation = operationList.at( i ).toElement();
if ( operation.attribute( QStringLiteral( "name" ) ) == QLatin1String( "GetFeature" ) )
QString name = operation.attribute( QStringLiteral( "name" ) );

// Search for DCP/HTTP
QDomNodeList operationHttpList = operation.elementsByTagName( QStringLiteral( "HTTP" ) );
for ( int j = 0; j < operationHttpList.size(); ++j )
{
QDomElement value = operationHttpList.at( j ).toElement();
QDomNodeList httpGetMethodList = value.elementsByTagName( QStringLiteral( "Get" ) );
QDomNodeList httpPostMethodList = value.elementsByTagName( QStringLiteral( "Post" ) );
if ( httpGetMethodList.size() > 0 )
{
mCaps.operationGetEndpoints[name] = httpGetMethodList.at( 0 ).toElement().attribute( QStringLiteral( "href" ) );
QgsDebugMsgLevel( QStringLiteral( "Adding DCP Get %1 %2" ).arg( name, mCaps.operationGetEndpoints[name] ), 3 );
}
if ( httpPostMethodList.size() > 0 )
{
mCaps.operationPostEndpoints[name] = httpPostMethodList.at( 0 ).toElement().attribute( QStringLiteral( "href" ) );
QgsDebugMsgLevel( QStringLiteral( "Adding DCP Post %1 %2" ).arg( name, mCaps.operationPostEndpoints[name] ), 3 );
}
}

if ( name == QLatin1String( "GetFeature" ) )
{
QDomNodeList operationContraintList = operation.elementsByTagName( QStringLiteral( "Constraint" ) );
for ( int j = 0; j < operationContraintList.size(); ++j )
Expand Down
2 changes: 2 additions & 0 deletions src/providers/wfs/qgswfscapabilities.h
Expand Up @@ -98,6 +98,8 @@ class QgsWfsCapabilities : public QgsWfsRequest
QList<Function> functionList;
bool useEPSGColumnFormat; // whether to use EPSG:XXXX srsname
QList< QString > outputFormats;
QgsStringMap operationGetEndpoints;
QgsStringMap operationPostEndpoints;

QSet< QString > setAllTypenames;
QMap< QString, QString> mapUnprefixedTypenameToPrefixedTypename;
Expand Down
32 changes: 32 additions & 0 deletions src/providers/wfs/qgswfsdatasourceuri.cpp
Expand Up @@ -165,6 +165,28 @@ QUrl QgsWFSDataSourceURI::baseURL( bool bIncludeServiceWFS ) const
return url;
}

QUrl QgsWFSDataSourceURI::requestUrl( const QString &request, const Method &method ) const
{
QString endpoint;
switch ( method )
{
case Post:
endpoint = mPostEndpoints.contains( request ) ?
mPostEndpoints[ request ] : mURI.param( QgsWFSConstants::URI_PARAM_URL );
break;
default:
case Get:
endpoint = mGetEndpoints.contains( request ) ?
mGetEndpoints[ request ] : mURI.param( QgsWFSConstants::URI_PARAM_URL );
break;
}
QUrl url( endpoint );
url.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
if ( ! request.isEmpty() )
url.addQueryItem( QStringLiteral( "REQUEST" ), request );
return url;
}

QString QgsWFSDataSourceURI::version() const
{
if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_VERSION ) )
Expand Down Expand Up @@ -298,3 +320,13 @@ QString QgsWFSDataSourceURI::build( const QString &baseUri,
uri.mURI.setParam( QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX, QStringLiteral( "1" ) );
return uri.uri();
}

void QgsWFSDataSourceURI::setGetEndpoints( const QgsStringMap &map )
{
mGetEndpoints = map;
}

void QgsWFSDataSourceURI::setPostEndpoints( const QgsStringMap &map )
{
mPostEndpoints = map;
}
18 changes: 18 additions & 0 deletions src/providers/wfs/qgswfsdatasourceuri.h
Expand Up @@ -75,6 +75,13 @@ class QgsWFSDataSourceURI
{
public:

//! Http method for DCP URIs
enum Method
{
Get,
Post
};

explicit QgsWFSDataSourceURI( const QString &uri );

//! Return the URI, avoiding expansion of authentication configuration, which is handled during network access
Expand All @@ -83,6 +90,9 @@ class QgsWFSDataSourceURI
//! Return base URL (with SERVICE=WFS parameter if bIncludeServiceWFS=true)
QUrl baseURL( bool bIncludeServiceWFS = true ) const;

//! Return request URL with SERVICE=WFS parameter)
QUrl requestUrl( const QString &request, const Method &method = Method::Get ) const;

//! Get WFS version. Can be auto, 1.0.0, 1.1.0 or 2.0.0.
QString version() const;

Expand Down Expand Up @@ -150,9 +160,17 @@ class QgsWFSDataSourceURI
const QString &sql = QString(),
bool restrictToCurrentViewExtent = false );

//! Set Get DCP endpoints
void setGetEndpoints( const QgsStringMap &map );

//! Set Post DCP endpoints
void setPostEndpoints( const QgsStringMap &map );

private:
QgsDataSourceUri mURI;
QgsWFSAuthorization mAuth;
QgsStringMap mGetEndpoints;
QgsStringMap mPostEndpoints;
};


Expand Down
5 changes: 2 additions & 3 deletions src/providers/wfs/qgswfsdescribefeaturetype.cpp
Expand Up @@ -16,16 +16,15 @@
#include "qgswfsdescribefeaturetype.h"
#include "qgswfsutils.h"

QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( const QString &uri )
QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( QgsWFSDataSourceURI &uri )
: QgsWfsRequest( uri )
{
}

bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion,
const QString &typeName, bool forceSingularTypeName )
{
QUrl url( baseURL() );
url.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
QUrl url( mUri.requestUrl( QStringLiteral( "DescribeFeatureType" ) ) );
url.addQueryItem( QStringLiteral( "VERSION" ), WFSVersion );
// The specs are not consistent: is it singular in 1.0.x and plural in 2.0.0?
// see http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#147
Expand Down
2 changes: 1 addition & 1 deletion src/providers/wfs/qgswfsdescribefeaturetype.h
Expand Up @@ -22,7 +22,7 @@ class QgsWFSDescribeFeatureType : public QgsWfsRequest
{
Q_OBJECT
public:
explicit QgsWFSDescribeFeatureType( const QString &uri );
explicit QgsWFSDescribeFeatureType( QgsWFSDataSourceURI &uri );

//! Issue the request
bool requestFeatureType( const QString &WFSVersion, const QString &typeName,
Expand Down
9 changes: 5 additions & 4 deletions src/providers/wfs/qgswfsfeatureiterator.cpp
Expand Up @@ -35,7 +35,7 @@
#include <QStyle>

QgsWFSFeatureHitsAsyncRequest::QgsWFSFeatureHitsAsyncRequest( QgsWFSDataSourceURI &uri )
: QgsWfsRequest( uri.uri() )
: QgsWfsRequest( uri )
, mNumberMatched( -1 )
{
connect( this, &QgsWfsRequest::downloadFinished, this, &QgsWFSFeatureHitsAsyncRequest::hitsReplyFinished );
Expand Down Expand Up @@ -77,7 +77,7 @@ QString QgsWFSFeatureHitsAsyncRequest::errorMessageWithReason( const QString &re
// -------------------------

QgsWFSFeatureDownloader::QgsWFSFeatureDownloader( QgsWFSSharedData *shared )
: QgsWfsRequest( shared->mURI.uri() )
: QgsWfsRequest( shared->mURI )
, mShared( shared )
, mStop( false )
, mProgressDialogShowImmediately( false )
Expand Down Expand Up @@ -187,8 +187,7 @@ QString QgsWFSFeatureDownloader::sanitizeFilter( QString filter )

QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool forHits )
{
QUrl getFeatureUrl( mShared->mURI.baseURL() );
getFeatureUrl.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetFeature" ) );
QUrl getFeatureUrl( mShared->mURI.requestUrl( QStringLiteral( "GetFeature" ) ) );
getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion );

QString typenames;
Expand Down Expand Up @@ -375,6 +374,8 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo
namespaces );
}

QgsDebugMsgLevel( QStringLiteral( "WFS GetFeature URL: %1" ).arg( getFeatureUrl.toDisplayString( ) ), 2 );

return getFeatureUrl;
}

Expand Down
16 changes: 10 additions & 6 deletions src/providers/wfs/qgswfsprovider.cpp
Expand Up @@ -419,7 +419,7 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
concatenatedTypenames += typeName;
}

QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI.uri() );
QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI );
if ( !describeFeatureType.requestFeatureType( mShared->mWFSVersion,
concatenatedTypenames ) )
{
Expand Down Expand Up @@ -1168,7 +1168,7 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute,
{
fields.clear();

QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI.uri() );
QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI );
if ( !describeFeatureType.requestFeatureType( mShared->mWFSVersion,
mShared->mURI.typeName(), forceSingularTypeNames ) )
{
Expand Down Expand Up @@ -1450,7 +1450,7 @@ bool QgsWFSProvider::sendTransactionDocument( const QDomDocument &doc, QDomDocum
return false;
}

QgsWFSTransactionRequest request( mShared->mURI.uri() );
QgsWFSTransactionRequest request( mShared->mURI );
return request.send( doc, serverResponse );
}

Expand All @@ -1464,11 +1464,13 @@ QDomElement QgsWFSProvider::createTransactionElement( QDomDocument &doc ) const
transactionElem.setAttribute( QStringLiteral( "service" ), QStringLiteral( "WFS" ) );
transactionElem.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );

QUrl describeFeatureTypeURL( mShared->mURI.baseURL() );
QUrl describeFeatureTypeURL = mShared->mURI.requestUrl( QStringLiteral( "DescribeFeatureType" ) );
// For tests (since the URL contains part of random data, we need to replace it with a fixed content)
if ( mShared->mURI.baseURL().toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
if ( describeFeatureTypeURL.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
{
describeFeatureTypeURL = QUrl( QStringLiteral( "http://fake_qgis_http_endpoint" ) );
describeFeatureTypeURL.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
describeFeatureTypeURL.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
}
describeFeatureTypeURL.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
//TODO: proper support of 2.0.0, for now hardcoded
describeFeatureTypeURL.addQueryItem( QgsWFSUtils::typeNameParameterForVersion( WfsVersion ).toUpper(), mShared->mURI.typeName() );
Expand Down Expand Up @@ -1562,6 +1564,8 @@ bool QgsWFSProvider::getCapabilities()

const QgsWfsCapabilities::Capabilities caps = getCapabilities.capabilities();
mShared->mCaps = caps;
mShared->mURI.setGetEndpoints( caps.operationGetEndpoints );
mShared->mURI.setPostEndpoints( caps.operationPostEndpoints );
}

mShared->mWFSVersion = mShared->mCaps.version;
Expand Down
11 changes: 9 additions & 2 deletions src/providers/wfs/qgswfsrequest.cpp
Expand Up @@ -25,15 +25,15 @@
#include <QNetworkCacheMetaData>
#include <QCryptographicHash> // just for testin file:// fake_qgis_http_endpoint hack

QgsWfsRequest::QgsWfsRequest( const QString &uri )
QgsWfsRequest::QgsWfsRequest( const QgsWFSDataSourceURI &uri )
: mUri( uri )
, mErrorCode( QgsWfsRequest::NoError )
, mIsAborted( false )
, mForceRefresh( false )
, mTimedout( false )
, mGotNonEmptyResponse( false )
{
QgsDebugMsg( "theUri = " + uri );
QgsDebugMsg( "theUri = " + uri.uri( ) );
connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::requestTimedOut, this, &QgsWfsRequest::requestTimedOut );
}

Expand All @@ -48,6 +48,11 @@ void QgsWfsRequest::requestTimedOut( QNetworkReply *reply )
mTimedout = true;
}

QUrl QgsWfsRequest::requestUrl( const QString &request ) const
{
return mUri.requestUrl( request );
}

bool QgsWfsRequest::sendGET( const QUrl &url, bool synchronous, bool forceRefresh, bool cache )
{
abort(); // cancel previous
Expand All @@ -61,6 +66,8 @@ bool QgsWfsRequest::sendGET( const QUrl &url, bool synchronous, bool forceRefres
mResponse.clear();

QUrl modifiedUrl( url );

// Specific code for testing
if ( modifiedUrl.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) )
{
// Just for testing with local files instead of http:// resources
Expand Down
11 changes: 6 additions & 5 deletions src/providers/wfs/qgswfsrequest.h
Expand Up @@ -27,7 +27,7 @@ class QgsWfsRequest : public QObject
{
Q_OBJECT
public:
explicit QgsWfsRequest( const QString &uri );
explicit QgsWfsRequest( const QgsWFSDataSourceURI &uri );

~QgsWfsRequest() override;

Expand All @@ -54,6 +54,9 @@ class QgsWfsRequest : public QObject
//! \brief Return server response (after download/post)
QByteArray response() const { return mResponse; }

//! Return the url for a WFS request
QUrl requestUrl( const QString &request ) const;

public slots:
//! Abort network request immediately
void abort();
Expand Down Expand Up @@ -100,12 +103,10 @@ class QgsWfsRequest : public QObject

protected:

//! base service URL
QUrl baseURL() const { return mUri.baseURL(); }

/**
* Return (translated) error message, composed with a
(possibly translated, but sometimes coming from server) reason */
* (possibly translated, but sometimes coming from server) reason
*/
virtual QString errorMessageWithReason( const QString &reason ) = 0;

//! Return experiation delay in second
Expand Down
10 changes: 4 additions & 6 deletions src/providers/wfs/qgswfsshareddata.cpp
Expand Up @@ -1170,15 +1170,14 @@ QgsGmlStreamingParser *QgsWFSSharedData::createParser()


QgsWFSFeatureHitsRequest::QgsWFSFeatureHitsRequest( QgsWFSDataSourceURI &uri )
: QgsWfsRequest( uri.uri() )
: QgsWfsRequest( uri )
{
}

int QgsWFSFeatureHitsRequest::getFeatureCount( const QString &WFSVersion,
const QString &filter )
{
QUrl getFeatureUrl( mUri.baseURL() );
getFeatureUrl.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetFeature" ) );
QUrl getFeatureUrl( mUri.requestUrl( QStringLiteral( "GetFeature" ) ) );
getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), WFSVersion );
if ( WFSVersion.startsWith( QLatin1String( "2.0" ) ) )
getFeatureUrl.addQueryItem( QStringLiteral( "TYPENAMES" ), mUri.typeName() );
Expand Down Expand Up @@ -1232,14 +1231,13 @@ QString QgsWFSFeatureHitsRequest::errorMessageWithReason( const QString &reason


QgsWFSSingleFeatureRequest::QgsWFSSingleFeatureRequest( QgsWFSSharedData *shared )
: QgsWfsRequest( shared->mURI.uri() ), mShared( shared )
: QgsWfsRequest( shared->mURI ), mShared( shared )
{
}

QgsRectangle QgsWFSSingleFeatureRequest::getExtent()
{
QUrl getFeatureUrl( mUri.baseURL() );
getFeatureUrl.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "GetFeature" ) );
QUrl getFeatureUrl( mUri.requestUrl( QStringLiteral( "GetFeature" ) ) );
getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion );
if ( mShared->mWFSVersion .startsWith( QLatin1String( "2.0" ) ) )
getFeatureUrl.addQueryItem( QStringLiteral( "TYPENAMES" ), mUri.typeName() );
Expand Down
4 changes: 2 additions & 2 deletions src/providers/wfs/qgswfstransactionrequest.cpp
Expand Up @@ -16,14 +16,14 @@
#include "qgswfstransactionrequest.h"
#include "qgslogger.h"

QgsWFSTransactionRequest::QgsWFSTransactionRequest( const QString &uri )
QgsWFSTransactionRequest::QgsWFSTransactionRequest( const QgsWFSDataSourceURI &uri )
: QgsWfsRequest( uri )
{
}

bool QgsWFSTransactionRequest::send( const QDomDocument &doc, QDomDocument &serverResponse )
{
QUrl url( baseURL() );
QUrl url( mUri.requestUrl( QString( ) ) );

QgsDebugMsg( doc.toString() );

Expand Down
2 changes: 1 addition & 1 deletion src/providers/wfs/qgswfstransactionrequest.h
Expand Up @@ -22,7 +22,7 @@ class QgsWFSTransactionRequest : public QgsWfsRequest
{
Q_OBJECT
public:
explicit QgsWFSTransactionRequest( const QString &uri );
explicit QgsWFSTransactionRequest( const QgsWFSDataSourceURI &uri );

//! Send the transaction document and return the server response
bool send( const QDomDocument &doc, QDomDocument &serverResponse );
Expand Down

0 comments on commit f1b5987

Please sign in to comment.