view rdf/RDFTransformFactory.cpp @ 1879:652c5360e682

Ensure transforms are populated before instantiateDefaultPluginFor runs - otherwise if we have prior knowledge of a transform id, we can find ourselves trying to instantiate it before the plugin factory has heard of it and e.g. knows which server to use
author Chris Cannam
date Thu, 25 Jun 2020 12:20:06 +0100
parents d094598f84bd
children
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2008-2012 QMUL.
   
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "RDFTransformFactory.h"

#include <map>
#include <vector>

#include <QTextStream>
#include <QUrl>

#include <iostream>
#include <cmath>

#include "PluginRDFIndexer.h"
#include "PluginRDFDescription.h"
#include "base/ProgressReporter.h"
#include "plugin/PluginIdentifier.h"

#include "transform/TransformFactory.h"

#include <dataquay/BasicStore.h>
#include <dataquay/PropertyObject.h>

using Dataquay::Uri;
using Dataquay::Node;
using Dataquay::Nodes;
using Dataquay::Triple;
using Dataquay::Triples;
using Dataquay::BasicStore;
using Dataquay::PropertyObject;


class RDFTransformFactoryImpl
{
public:
    RDFTransformFactoryImpl(QString url);
    virtual ~RDFTransformFactoryImpl();
    
    bool isRDF();
    bool isOK();
    QString getErrorString() const;

    std::vector<Transform> getTransforms(ProgressReporter *);

    static QString writeTransformToRDF(const Transform &, QString);

protected:
    BasicStore *m_store;
    QString m_urlString;
    QString m_errorString;
    bool m_isRDF;
    bool setOutput(Transform &, QString);
    bool setParameters(Transform &, QString);
};


QString
RDFTransformFactory::getKnownExtensions()
{
    return "*.rdf *.n3 *.ttl";
}

RDFTransformFactory::RDFTransformFactory(QString url) :
    m_d(new RDFTransformFactoryImpl(url)) 
{
}

RDFTransformFactory::~RDFTransformFactory()
{
    delete m_d;
}

bool
RDFTransformFactory::isRDF()
{
    return m_d->isRDF();
}

bool
RDFTransformFactory::isOK()
{
    return m_d->isOK();
}

QString
RDFTransformFactory::getErrorString() const
{
    return m_d->getErrorString();
}

std::vector<Transform>
RDFTransformFactory::getTransforms(ProgressReporter *r)
{
    return m_d->getTransforms(r);
}

QString
RDFTransformFactory::writeTransformToRDF(const Transform &t, QString f)
{
    return RDFTransformFactoryImpl::writeTransformToRDF(t, f);
}

RDFTransformFactoryImpl::RDFTransformFactoryImpl(QString url) :
    m_store(new BasicStore),
    m_urlString(url),
    m_isRDF(false)
{
    //!!! retrieve data if remote... then
    m_store->addPrefix("vamp", Uri("http://purl.org/ontology/vamp/"));
    try {
        QUrl qurl;
        if (url.startsWith("file:")) {
            qurl = QUrl(url);
        } else {
            qurl = QUrl::fromLocalFile(url);
        }
        m_store->import(qurl, BasicStore::ImportIgnoreDuplicates);
        m_isRDF = true;
    } catch (const std::exception &e) {
        // The file is not RDF -- we report this by returning false
        // from isRDF (because we have not reached the m_isRDF = true
        // line above), but we also set the error string
        m_errorString = e.what();
    }
}

RDFTransformFactoryImpl::~RDFTransformFactoryImpl()
{
    delete m_store;
}

bool
RDFTransformFactoryImpl::isRDF()
{
    return m_isRDF;
}

bool
RDFTransformFactoryImpl::isOK()
{
    return m_isRDF && (m_errorString == "");
}

QString
RDFTransformFactoryImpl::getErrorString() const
{
    return m_errorString;
}

std::vector<Transform>
RDFTransformFactoryImpl::getTransforms(ProgressReporter *)
{
    std::vector<Transform> transforms;

    if (!m_isRDF) return transforms;
    
    std::map<QString, Transform> uriTransformMap;

    Nodes tnodes = m_store->match
        (Triple(Node(), Uri("a"), m_store->expand("vamp:Transform"))).subjects();

    PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();

    foreach (Node tnode, tnodes) {

        Node pnode = m_store->complete
            (Triple(tnode, m_store->expand("vamp:plugin"), Node()));

        if (pnode == Node()) {
            cerr << "RDFTransformFactory: WARNING: No vamp:plugin for "
                 << "vamp:Transform node " << tnode
                 << ", skipping this transform" << endl;
            continue;
        }

        QString transformUri = tnode.value;
        QString pluginUri = pnode.value;

        QString pluginId = indexer->getIdForPluginURI(pluginUri);
        if (pluginId == "") {
            cerr << "RDFTransformFactory: WARNING: Unknown plugin <"
                 << pluginUri << "> for transform <"
                 << transformUri << ">, skipping this transform"
                 << endl;
            continue;
        }

        Transform transform;
        transform.setPluginIdentifier(pluginId);

        if (!setOutput(transform, transformUri)) {
            return transforms;
        }

        if (!setParameters(transform, transformUri)) {
            return transforms;
        }

        uriTransformMap[transformUri] = transform;

        static const char *optionals[] = {
            "program",
            "summary_type",
            "step_size",
            "block_size",
            "window_type",
            "sample_rate",
            "start", 
            "duration",
            "plugin_version"
        };
        
        for (int j = 0; j < int(sizeof(optionals)/sizeof(optionals[0])); ++j) {

            QString optional = optionals[j];

            Node onode = m_store->complete
                (Triple(Uri(transformUri),
                        m_store->expand(QString("vamp:") + optional), Node()));

            if (onode.type != Node::Literal) continue;

            if (optional == "program") {
                transform.setProgram(onode.value);
            } else if (optional == "summary_type") {
                transform.setSummaryType
                    (transform.stringToSummaryType(onode.value));
            } else if (optional == "step_size") {
                transform.setStepSize(onode.value.toUInt());
            } else if (optional == "block_size") {
                transform.setBlockSize(onode.value.toUInt());
            } else if (optional == "window_type") {
                transform.setWindowType
                    (Window<float>::getTypeForName
                     (onode.value.toLower().toStdString()));
            } else if (optional == "sample_rate") {
                transform.setSampleRate(onode.value.toFloat());
            } else if (optional == "start") {
                RealTime start = RealTime::fromXsdDuration(onode.value.toStdString());
                transform.setStartTime(start);
            } else if (optional == "duration") {
                RealTime duration = RealTime::fromXsdDuration(onode.value.toStdString());
                transform.setDuration(duration);
                if (duration == RealTime::zeroTime) {
                    cerr << "\nRDFTransformFactory: WARNING: Duration is specified as \"" << onode.value << "\" in RDF file,\n    but this evaluates to zero when parsed as an xsd:duration datatype.\n    The duration property will therefore be ignored.\n    To specify start time and duration use the xsd:duration format,\n    for example \"PT2.5S\"^^xsd:duration (for 2.5 seconds).\n\n";
                }
            } else if (optional == "plugin_version") {
                transform.setPluginVersion(onode.value);
            } else {
                cerr << "RDFTransformFactory: ERROR: Inconsistent optionals lists (unexpected optional \"" << optional << "\"" << endl;
            }
        }

        cerr << "RDFTransformFactory: NOTE: Transform is: " << endl;
        cerr << transform.toXmlString() << endl;

        transforms.push_back(transform);
    }
        
    return transforms;
}

bool
RDFTransformFactoryImpl::setOutput(Transform &transform,
                                   QString transformUri)
{
    Node outputNode = m_store->complete
        (Triple(Uri(transformUri), m_store->expand("vamp:output"), Node()));
    
    if (outputNode == Node()) return true;

    if (outputNode.type != Node::URI && outputNode.type != Node::Blank) {
        m_errorString = QString("vamp:output for output of transform <%1> is not a URI or blank node").arg(transformUri);
        return false;
    }

    // Now, outputNode might be the subject of a triple within m_store
    // that tells us the vamp:identifier, or it might be the subject
    // of a triple within the indexer that tells us it

    Node identNode = m_store->complete
        (Triple(outputNode, m_store->expand("vamp:identifier"), Node()));

    if (identNode == Node()) {
        PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();
        const BasicStore *index = indexer->getIndex();
        identNode = index->complete
            (Triple(outputNode, index->expand("vamp:identifier"), Node()));
    }

    if (identNode == Node() || identNode.type != Node::Literal) {
        m_errorString = QString("No vamp:identifier found for output of transform <%1>, or vamp:identifier is not a literal").arg(transformUri);
        return false;
    }

    transform.setOutput(identNode.value);

    return true;
}
        

bool
RDFTransformFactoryImpl::setParameters(Transform &transform,
                                       QString transformUri)
{
    Nodes bindings = m_store->match
        (Triple(Uri(transformUri), m_store->expand("vamp:parameter_binding"), Node())).objects();
    
    foreach (Node binding, bindings) {

        Node paramNode = m_store->complete
            (Triple(binding, m_store->expand("vamp:parameter"), Node()));

        if (paramNode == Node()) {
            cerr << "RDFTransformFactoryImpl::setParameters: No vamp:parameter for binding " << binding << endl;
            continue;
        }

        Node valueNode = m_store->complete
            (Triple(binding, m_store->expand("vamp:value"), Node()));

        if (paramNode == Node()) {
            cerr << "RDFTransformFactoryImpl::setParameters: No vamp:value for binding " << binding << endl;
            continue;
        }
        
        // As with output above, paramNode might be the subject of a
        // triple within m_store that tells us the vamp:identifier, or
        // it might be the subject of a triple within the indexer that
        // tells us it

        Node idNode = m_store->complete
            (Triple(paramNode, m_store->expand("vamp:identifier"), Node()));

        if (idNode == Node()) {
            PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance();
            const BasicStore *index = indexer->getIndex();
            idNode = index->complete
                (Triple(paramNode, index->expand("vamp:identifier"), Node()));
        }

        if (idNode == Node() || idNode.type != Node::Literal) {
            cerr << "RDFTransformFactoryImpl::setParameters: No vamp:identifier for parameter " << paramNode << endl;
            continue;
        }
        
        transform.setParameter(idNode.value, valueNode.value.toFloat());
    }

    return true;
}

QString
RDFTransformFactoryImpl::writeTransformToRDF(const Transform &transform,
                                             QString uri)
{
    QString str;
    QTextStream s(&str);

    // assumes the usual prefixes are available; requires that uri be
    // a local fragment (e.g. ":transform") rather than a uri enclosed
    // in <>, so that we can suffix it if need be

    QString pluginId = transform.getPluginIdentifier();
    QString pluginUri = PluginRDFIndexer::getInstance()->getURIForPluginId(pluginId);

    if (pluginUri != "") {
        s << uri << " a vamp:Transform ;" << endl;
        s << "    vamp:plugin <" << QUrl(pluginUri).toEncoded().data() << "> ;" << endl;
    } else {
        cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No plugin URI available for plugin id \"" << pluginId << "\", writing synthetic plugin and library resources" << endl;
        QString type, soname, label;
        PluginIdentifier::parseIdentifier(pluginId, type, soname, label);
        s << uri << "_plugin a vamp:Plugin ;" << endl;
        s << "    vamp:identifier \"" << label << "\" .\n" << endl;
        s << uri << "_library a vamp:PluginLibrary ;" << endl;
        s << "    vamp:identifier \"" << soname << "\" ;" << endl;
        s << "    vamp:available_plugin " << uri << "_plugin .\n" << endl;
        s << uri << " a vamp:Transform ;" << endl;
        s << "    vamp:plugin " << uri << "_plugin ;" << endl;
    }

    PluginRDFDescription description(pluginId);
    QString outputId = transform.getOutput();
    QString outputUri = description.getOutputUri(outputId);

    if (transform.getOutput() != "" && outputUri == "") {
        cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No output URI available for transform output id \"" << transform.getOutput() << "\", writing a synthetic output resource" << endl;
    }

    if (transform.getStepSize() != 0) {
        s << "    vamp:step_size \"" << transform.getStepSize() << "\"^^xsd:int ; " << endl;
    }
    if (transform.getBlockSize() != 0) {
        s << "    vamp:block_size \"" << transform.getBlockSize() << "\"^^xsd:int ; " << endl;
    }
    if (transform.getWindowType() != HanningWindow) {
        s << "    vamp:window_type \"" <<
            Window<float>::getNameForType(transform.getWindowType()).c_str()
          << "\" ; " << endl;
    }
    if (transform.getStartTime() != RealTime::zeroTime) {
        s << "    vamp:start \"" << transform.getStartTime().toXsdDuration().c_str() << "\"^^xsd:duration ; " << endl;
    }
    if (transform.getDuration() != RealTime::zeroTime) {
        s << "    vamp:duration \"" << transform.getDuration().toXsdDuration().c_str() << "\"^^xsd:duration ; " << endl;
    }
    if (transform.getSampleRate() != 0) {
        s << "    vamp:sample_rate \"" << transform.getSampleRate() << "\"^^xsd:float ; " << endl;
    }
    if (transform.getPluginVersion() != "") {
        s << "    vamp:plugin_version \"\"\"" << transform.getPluginVersion() << "\"\"\" ; " << endl;
    }
    
    QString program = transform.getProgram();
    if (program != "") {
        s << "    vamp:program \"\"\"" << program << "\"\"\" ;" << endl;
    }

    QString summary = transform.summaryTypeToString(transform.getSummaryType());
    if (summary != "") {
        s << "    vamp:summary_type \"" << summary << "\" ;" << endl;
    }

    Transform::ParameterMap parameters = transform.getParameters();
    for (Transform::ParameterMap::const_iterator i = parameters.begin();
         i != parameters.end(); ++i) {
        QString name = i->first;
        float value = i->second;
        s << "    vamp:parameter_binding [" << endl;
        s << "        vamp:parameter [ vamp:identifier \"" << name << "\" ] ;" << endl;
        s << "        vamp:value \"" << value << "\"^^xsd:float ;" << endl;
        s << "    ] ;" << endl;
    }

    if (outputUri != "") {
        s << "    vamp:output <" << QUrl(outputUri).toEncoded().data() << "> ." << endl;
    } else if (outputId != "") {
        s << "    vamp:output [ vamp:identifier \"" << outputId << "\" ] ." << endl;
    } else {
        s << "    ." << endl;
    }

    return str;
}