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.