32 #include "nlohmann/json.hpp"
33 #include "inja/inja.hpp"
35 using namespace nlohmann;
43 const auto constParameters { parameters( context ) };
44 for (
const auto &p : constParameters )
47 result[p.name()] = p.value( context );
49 const auto match { path().match( context.
request()->
url().toString() ) };
50 if ( match.hasMatch() )
52 const auto constNamed { path().namedCaptureGroups() };
54 for (
const auto &name : constNamed )
56 if ( ! name.isEmpty() )
57 result[name] = QUrlQuery( match.captured( name ) ).toString() ;
70 const auto constContentTypes( contentTypes() );
71 return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
88 for (
auto it = constContentTypes.constBegin();
89 it != constContentTypes.constEnd(); ++it )
91 const auto constValues = it.value();
92 for (
const auto &value : constValues )
94 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
102 QStringLiteral(
"Server" ),
103 Qgis::MessageLevel::Info );
111 switch ( contentType )
113 case QgsServerOgcApi::ContentType::HTML:
114 data[
"handler"] = schema( context );
115 if ( ! htmlMetadata.is_null() )
117 data[
"metadata"] = htmlMetadata;
119 htmlDump( data, context );
121 case QgsServerOgcApi::ContentType::GEOJSON:
122 case QgsServerOgcApi::ContentType::JSON:
123 case QgsServerOgcApi::ContentType::OPENAPI3:
126 case QgsServerOgcApi::ContentType::XML:
143 const auto match { path().match( url.path() ) };
144 if ( match.captured().count() > 0 )
146 url.setPath( urlBasePath + match.captured( 0 ) );
150 url.setPath( urlBasePath );
154 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
155 if ( suffixLength > 0 )
157 auto path {url.path()};
158 path.truncate( path.length() - ( suffixLength + 1 ) );
163 url.setPath( url.path() + extraPath );
167 if ( ! extension.isEmpty() )
170 QString path { url.path() };
171 while ( path.endsWith(
'/' ) )
175 url.setPath( path +
'.' + extension );
186 QDateTime time { QDateTime::currentDateTime() };
187 time.setTimeSpec( Qt::TimeSpec::UTC );
188 data[
"timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ;
210 "href", href( context,
"/",
215 {
"title", title !=
"" ? title : linkTitle() },
223 json links = json::array();
224 const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
225 for (
const auto &ct : constCts )
227 links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self :
228 QgsServerOgcApi::Rel::alternate ), ct,
241 const QRegularExpressionMatch match { path().match( context.
request()->
url().path( ) ) };
242 if ( ! match.hasMatch() )
246 const QString collectionId { match.captured( QStringLiteral(
"collectionId" ) ) };
248 return layerFromCollectionId( context, collectionId );
262 path += QLatin1String(
"/ogc/templates" );
265 path += QString::fromStdString( operationId() );
266 path += QLatin1String(
".html" );
273 context.
response()->
setHeader( QStringLiteral(
"Content-Type" ), QStringLiteral(
"text/html" ) );
274 auto path { templatePath( context ) };
275 if ( ! QFile::exists( path ) )
277 QgsMessageLog::logMessage( QStringLiteral(
"Template not found error: %1" ).arg( path ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
282 if ( ! f.open( QFile::ReadOnly | QFile::Text ) )
284 QgsMessageLog::logMessage( QStringLiteral(
"Could not open template file: %1" ).arg( path ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
291 QFileInfo pathInfo { path };
292 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
295 env.add_callback(
"json_dump", 0, [ = ]( Arguments & )
301 env.add_callback(
"path_append", 1, [ = ]( Arguments & args )
303 auto url { context.
request()->url() };
304 QFileInfo fi{ url.path() };
305 auto suffix { fi.suffix() };
306 auto fName { fi.filePath()};
307 if ( !suffix.isEmpty() )
309 fName.chop( suffix.length() + 1 );
312 while ( fName.endsWith(
'/' ) )
316 fName +=
'/' + QString::fromStdString( args.at( 0 )->get<std::string>( ) );
317 if ( !suffix.isEmpty() )
319 fName +=
'.' + suffix;
322 url.setPath( fi.filePath() );
323 return url.toString().toStdString();
327 env.add_callback(
"path_chomp", 1, [ = ]( Arguments & args )
329 QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
330 QFileInfo fi{ url.path() };
331 auto suffix { fi.suffix() };
332 auto fName { fi.filePath()};
333 fName.chop( suffix.length() + 1 );
335 fName = fName.replace( QRegularExpression( R
"raw(\/[^/]+$)raw" ), QString() );
336 if ( !suffix.isEmpty() )
338 fName +=
'.' + suffix;
341 url.setPath( fi.filePath() );
342 return url.toString().toStdString();
347 env.add_callback(
"links_filter", 3, [ = ]( Arguments & args )
349 json links = args.at( 0 )->get<json>( );
350 if ( ! links.is_array() )
352 links = json::array();
354 std::string key { args.at( 1 )->get<std::string>( ) };
355 std::string value { args.at( 2 )->get<std::string>( ) };
356 json result = json::array();
357 for (
const auto &l : links )
359 if ( l[key] == value )
361 result.push_back( l );
368 env.add_callback(
"content_type_name", 1, [ = ]( Arguments & args )
375 env.add_callback(
"nl2br", 1, [ = ]( Arguments & args )
377 QString text { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
378 return text.replace(
'\n', QLatin1String(
"<br>" ) ).toStdString();
384 env.add_callback(
"component_parameter", 1, [ = ]( Arguments & args )
386 json ret = json::array();
387 json ref = args.at( 0 )->get<json>( );
388 if ( ! ref.is_object() )
394 QString name = QString::fromStdString( ref[
"$ref"] );
395 name = name.split(
'/' ).last();
396 ret.push_back( data[
"components"][
"parameters"][name.toStdString()] );
398 catch ( std::exception & )
407 env.add_callback(
"static", 1, [ = ]( Arguments & args )
409 auto asset( args.at( 0 )->get<std::string>( ) );
412 if ( matchedPath ==
'/' )
416 return matchedPath.toStdString() +
"/static/" + asset;
419 context.
response()->
write( env.render_file( pathInfo.fileName().toStdString(), data ) );
421 catch ( std::exception &e )
423 QgsMessageLog::logMessage( QStringLiteral(
"Error parsing template file: %1 - %2" ).arg( path, e.what() ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
432 bool found {
false };
434 const QString extension { QFileInfo( request->
url().path() ).suffix().toUpper() };
435 if ( ! extension.isEmpty() )
437 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
439 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
447 QgsMessageLog::logMessage( QStringLiteral(
"The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Warning );
451 const QString accept { request->
header( QStringLiteral(
"Accept" ) ) };
452 if ( ! found && ! accept.isEmpty() )
454 const QString ctFromAccept { contentTypeForAccept( accept ) };
455 if ( ! ctFromAccept.isEmpty() )
458 auto it = constContentTypes.constBegin();
459 while ( ! found && it != constContentTypes.constEnd() )
461 int idx = it.value().indexOf( ctFromAccept );
472 QgsMessageLog::logMessage( QStringLiteral(
"The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Warning );
476 if ( ! contentTypes().contains( result ) )
479 bool found {
false };
482 const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
483 for (
const auto &ct : constCt )
505 QString path { url.path() };
506 const QFileInfo fi { path };
507 const QString suffix { fi.suffix() };
508 if ( ! suffix.isEmpty() )
510 path.chop( suffix.length() + 1 );
512 while ( path.endsWith(
'/' ) )
516 QRegularExpression re( R
"raw(\/[^/]+$)raw" );
517 for (
int i = 0; i < levels ; i++ )
519 path = path.replace( re, QString() );
522 QUrlQuery query( result );
523 QList<QPair<QString, QString> > qi;
524 const auto constItems { query.queryItems( ) };
525 for (
const auto &i : constItems )
527 if ( i.first.compare( QStringLiteral(
"MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
533 if ( ! path.endsWith(
'/' ) )
537 QUrlQuery resultQuery;
538 resultQuery.setQueryItems( qi );
539 result.setQuery( resultQuery );
540 result.setPath( path );
541 return result.toString();
547 if ( mapLayers.count() != 1 )
549 throw QgsServerApiNotFoundError( QStringLiteral(
"Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
551 return mapLayers.first();
558 {
"description",
"An error occurred." },
562 "application/json", {
565 {
"$ref",
"#/components/schemas/exception" }
592 mContentTypes.clear();
593 for (
const int &i : std::as_const( contentTypes ) )
601 mContentTypes = contentTypes;
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
Bad request error API exception.
The QgsServerApiContext class encapsulates the resources for a particular client request: the request...
const QgsProject * project() const
Returns the (possibly NULL) project.
QgsServerResponse * response() const
Returns the server response object.
const QgsServerRequest * request() const
Returns the server request object.
QgsServerInterface * serverInterface() const
Returns the server interface.
QString apiRootPath() const
Returns the API root path.
const QString matchedPath() const
Returns the initial part of the incoming request URL path that matches the API root path.
Internal server error API exception.
Not found error API exception.
this method is not yet implemented
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
std::string href(const QgsServerApiContext &context, const QString &extraPath=QString(), const QString &extension=QString()) const
Returns an URL to self, to be used for links to the current resources and as a base for constructing ...
virtual const QString templatePath(const QgsServerApiContext &context) const
Returns the HTML template path for the handler in the given context.
virtual const QString staticPath(const QgsServerApiContext &context) const
Returns the absolute path to the base directory where static resources for this handler are stored in...
virtual void handleRequest(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Handles the request within its context.
json jsonTags() const
Returns tags as JSON.
void htmlDump(const json &data, const QgsServerApiContext &context) const
Writes data as HTML to the response stream in context using a template.
virtual QgsServerOgcApi::ContentType defaultContentType() const
Returns the default response content type in case the client did not specifically ask for any particu...
QgsServerOgcApi::ContentType contentTypeFromRequest(const QgsServerRequest *request) const
Returns the content type from the request.
virtual ~QgsServerOgcApiHandler()
virtual json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
void setContentTypes(const QList< QgsServerOgcApi::ContentType > &contentTypes)
Set the content types to contentTypes.
void write(json &data, const QgsServerApiContext &context, const json &htmlMetadata=nullptr) const
Writes data to the context response stream, content-type is calculated from the context request,...
QList< QgsServerOgcApi::ContentType > contentTypes() const
Returns the list of content types this handler can serve, default to JSON and HTML.
virtual QVariantMap values(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
static json defaultResponse()
Returns the defaultResponse as JSON.
void jsonDump(json &data, const QgsServerApiContext &context, const QString &contentType=QStringLiteral("application/json")) const
Writes data to the context response stream as JSON (indented if debug is active), an optional content...
QgsVectorLayer * layerFromContext(const QgsServerApiContext &context) const
Returns a vector layer instance from the "collectionId" parameter of the path in the given context,...
QString contentTypeForAccept(const QString &accept) const
Looks for the first ContentType match in the accept header and returns its mime type,...
json link(const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType=QgsServerOgcApi::Rel::self, const QgsServerOgcApi::ContentType contentType=QgsServerOgcApi::ContentType::JSON, const std::string &title="") const
Builds and returns a link to the resource.
json links(const QgsServerApiContext &context) const
Returns all the links for the given request context.
static QString parentLink(const QUrl &url, int levels=1)
Returns a link to the parent page up to levels in the HTML hierarchy from the given url,...
void setContentTypesInt(const QList< int > &contentTypes)
Set the content types to contentTypes.
static QgsVectorLayer * layerFromCollectionId(const QgsServerApiContext &context, const QString &collectionId)
Returns a vector layer from the collectionId in the given context.
static QUrl sanitizeUrl(const QUrl &url)
Returns a sanitized url with extra slashes removed.
static QString contentTypeToExtension(const QgsServerOgcApi::ContentType &ct)
Returns the file extension for a ct (Content-Type).
static const QMap< QgsServerOgcApi::ContentType, QStringList > contentTypeMimes()
Returns a map of contentType => list of mime types.
static QgsServerOgcApi::ContentType contenTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
ContentType
Media types used for content negotiation, insert more specific first.
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static std::string contentTypeToStdString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static std::string mimeType(const QgsServerOgcApi::ContentType &contentType)
Returns the mime-type for the contentType or an empty string if not found.
static std::string relToString(const QgsServerOgcApi::Rel &rel)
Returns the string representation of rel attribute.
static const QHash< QgsServerOgcApi::ContentType, QList< QgsServerOgcApi::ContentType > > contentTypeAliases()
Returns contentType specializations (e.g.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
virtual QString header(const QString &name) const
Returns the header value.
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
virtual void setStatusCode(int code)=0
Set the http status code.
QString apiResourcesDirectory() const
Returns the server-wide base directory where HTML templates and static assets (e.g.
Represents a vector layer which manages a vector based data sets.