You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
11 KiB
381 lines
11 KiB
#include "serviceregistry.h"
|
|
#include "serviceregistry_p.h"
|
|
#include "qtmvvm_logging_p.h"
|
|
|
|
#include <QtCore/QGlobalStatic>
|
|
#include <QtCore/QRegularExpression>
|
|
#include <QtCore/QMetaProperty>
|
|
#include <QtCore/QCoreApplication>
|
|
#include <QtCore/QThread>
|
|
|
|
#include <qpluginfactory.h>
|
|
|
|
using namespace QtMvvm;
|
|
|
|
Q_GLOBAL_STATIC_WITH_ARGS(ServiceRegistry, _instance,
|
|
(new ServiceRegistryPrivate()))
|
|
|
|
ServiceRegistry::ServiceRegistry(ServiceRegistryPrivate *d_ptr) :
|
|
d(d_ptr)
|
|
{
|
|
// register quit scope cleanup
|
|
QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this](){
|
|
d->destroyServices(DestroyOnAppQuit);
|
|
});
|
|
// register destroy scope cleanup
|
|
qAddPostRoutine(&ServiceRegistryPrivate::appDestroyedHook);
|
|
}
|
|
|
|
ServiceRegistry::~ServiceRegistry()
|
|
{
|
|
d->destroyServices(DestroyOnRegistryDestroy);
|
|
}
|
|
|
|
ServiceRegistry *ServiceRegistry::instance()
|
|
{
|
|
return _instance;
|
|
}
|
|
|
|
bool ServiceRegistry::isRegistered(const QByteArray &iid) const
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
return d->services.contains(iid);
|
|
}
|
|
|
|
void ServiceRegistry::registerService(const QByteArray &iid, const QMetaObject *metaObject, DestructionScope scope, bool weak)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
if(d->serviceBlocked(iid))
|
|
throw ServiceExistsException(iid);
|
|
d->services.insert(iid, QSharedPointer<ServiceRegistryPrivate::MetaServiceInfo>::create(metaObject, weak, scope));
|
|
}
|
|
|
|
void ServiceRegistry::registerService(const QByteArray &iid, const QMetaObject *metaObject, bool weak)
|
|
{
|
|
registerService(iid, metaObject, DestroyOnAppDestroy, weak);
|
|
}
|
|
|
|
void ServiceRegistry::registerService(const QByteArray &iid, const std::function<QObject *(const QObjectList &)> &fn, QByteArrayList injectables, DestructionScope scope, bool weak)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
if(d->serviceBlocked(iid))
|
|
throw ServiceExistsException(iid);
|
|
d->services.insert(iid, QSharedPointer<ServiceRegistryPrivate::FnServiceInfo>::create(fn, std::move(injectables), weak, scope));
|
|
}
|
|
|
|
void ServiceRegistry::registerService(const QByteArray &iid, const std::function<QObject*(const QObjectList &)> &fn, QByteArrayList injectables, bool weak)
|
|
{
|
|
registerService(iid, fn, std::move(injectables), DestroyOnAppDestroy, weak);
|
|
}
|
|
|
|
void ServiceRegistry::registerService(QByteArray iid, QString pluginKey, QString pluginType, ServiceRegistry::DestructionScope scope, bool weak)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
if(d->serviceBlocked(iid))
|
|
throw ServiceExistsException(iid);
|
|
auto info = QSharedPointer<ServiceRegistryPrivate::PluginServiceInfo>::create(std::move(pluginKey),
|
|
std::move(pluginType),
|
|
std::move(iid),
|
|
weak,
|
|
scope);
|
|
d->services.insert(info->iid(), info);
|
|
}
|
|
|
|
QObject *ServiceRegistry::serviceObj(const QByteArray &iid)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
auto ref = d->services.value(iid);
|
|
if(ref)
|
|
return ref->instance(d.data(), iid);
|
|
else
|
|
throw ServiceDependencyException(iid);
|
|
}
|
|
|
|
void ServiceRegistry::injectServices(QObject *object)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
d->injectLocked(object);
|
|
}
|
|
|
|
QObject *ServiceRegistry::constructInjected(const QMetaObject *metaObject, QObject *parent)
|
|
{
|
|
QMutexLocker _(&d->serviceMutex);
|
|
return d->constructInjectedLocked(metaObject, parent);
|
|
}
|
|
|
|
// ------------- Private Implementation -------------
|
|
|
|
bool ServiceRegistryPrivate::serviceBlocked(const QByteArray &iid) const
|
|
{
|
|
auto svc = services.value(iid);
|
|
if(svc)
|
|
return !svc->replaceable();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
QObject *ServiceRegistryPrivate::constructInjectedLocked(const QMetaObject *metaObject, QObject *parent)
|
|
{
|
|
auto instance = metaObject->newInstance(Q_ARG(QObject*, parent));
|
|
if(!instance) {
|
|
throw ServiceConstructionException(QByteArrayLiteral("Failed to construct object of type ") +
|
|
metaObject->className() +
|
|
QByteArrayLiteral(" - make shure there is an invokable constructor of the format: Q_INVOKABLE MyClass(QObject*)"));
|
|
}
|
|
|
|
try {
|
|
injectLocked(instance);
|
|
return instance;
|
|
} catch (...) {
|
|
instance->deleteLater();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void ServiceRegistryPrivate::injectLocked(QObject *object)
|
|
{
|
|
static QRegularExpression nameRegex(QStringLiteral(R"__(^__qtmvvm_inject_(.+)$)__"),
|
|
QRegularExpression::OptimizeOnFirstUsageOption);
|
|
|
|
auto metaObject = object->metaObject();
|
|
for(auto i = 0; i < metaObject->propertyCount(); i++) {
|
|
auto prop = metaObject->property(i);
|
|
auto match = nameRegex.match(QString::fromUtf8(prop.name()));
|
|
if(match.hasMatch()) {
|
|
auto tPropIndex = metaObject->indexOfProperty(qUtf8Printable(match.captured(1)));
|
|
if(tPropIndex == -1) {
|
|
logWarning().noquote() << "Found hint to inject property"
|
|
<< match.captured(1)
|
|
<< "but no property of that name was found";
|
|
continue;
|
|
}
|
|
auto tProp = metaObject->property(tPropIndex);
|
|
|
|
auto iid = prop.read(object).toByteArray();
|
|
auto ref = services.value(iid);
|
|
if(!ref)
|
|
throw ServiceDependencyException(iid);
|
|
auto injObj = ref->instance(this, iid);
|
|
auto variant = QVariant::fromValue(injObj);
|
|
if(!variant.convert(tProp.userType())) {
|
|
throw ServiceConstructionException("Failed to convert QObject to interface with iid \"" +
|
|
iid +
|
|
"\". Use QtMvvm::registerInterfaceConverter to make it convertable "
|
|
"or change the property's type to \"QObject*\"");
|
|
}
|
|
tProp.write(object, variant);
|
|
}
|
|
}
|
|
|
|
// if available, call qtmvvm_init
|
|
auto initMethod = metaObject->indexOfMethod("qtmvvm_init()");
|
|
if(initMethod != -1) {
|
|
auto method = metaObject->method(initMethod);
|
|
method.invoke(object);
|
|
}
|
|
}
|
|
|
|
void ServiceRegistryPrivate::destroyServices(ServiceRegistry::DestructionScope scope)
|
|
{
|
|
QMutexLocker _(&serviceMutex);
|
|
logDebug() << "Beginning destruction for services in scope:" << scope;
|
|
for(auto it = services.begin(); it != services.end();) {
|
|
if(it.value()->needsDestroy(scope)) {
|
|
logDebug() << "Destroying service:" << it.key();
|
|
it = services.erase(it);
|
|
} else
|
|
it++;
|
|
}
|
|
logDebug() << "Finished destruction for services in scope:" << scope;
|
|
}
|
|
|
|
void ServiceRegistryPrivate::appDestroyedHook()
|
|
{
|
|
if(_instance.exists() && !_instance.isDestroyed())
|
|
_instance->d->destroyServices(ServiceRegistry::DestroyOnAppDestroy);
|
|
}
|
|
|
|
|
|
|
|
ServiceRegistryPrivate::ServiceInfo::ServiceInfo(bool weak, ServiceRegistry::DestructionScope scope) :
|
|
_weak{weak},
|
|
_scope{scope}
|
|
{}
|
|
|
|
ServiceRegistryPrivate::ServiceInfo::~ServiceInfo()
|
|
{
|
|
if(_instance && _scope != ServiceRegistry::DestroyNever) {
|
|
if(_closingDown)
|
|
delete _instance;
|
|
else
|
|
QMetaObject::invokeMethod(_instance, "deleteLater");
|
|
}
|
|
}
|
|
|
|
bool ServiceRegistryPrivate::ServiceInfo::replaceable() const
|
|
{
|
|
return _weak && !_instance;
|
|
}
|
|
|
|
bool ServiceRegistryPrivate::ServiceInfo::needsDestroy(ServiceRegistry::DestructionScope scope) const
|
|
{
|
|
if(_scope <= scope) {
|
|
_closingDown = true;
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
QObject *ServiceRegistryPrivate::ServiceInfo::instance(ServiceRegistryPrivate *d, const QByteArray &iid)
|
|
{
|
|
if(!_instance) {
|
|
_instance = construct(d);
|
|
if(!_instance)
|
|
throw ServiceConstructionException("Failed to construct service of type " +
|
|
iid +
|
|
" with unknown error");
|
|
if(_instance->thread() != qApp->thread())
|
|
_instance->moveToThread(qApp->thread());
|
|
logDebug() << "Constructed service of type" << iid;
|
|
}
|
|
return _instance;
|
|
}
|
|
|
|
|
|
|
|
ServiceRegistryPrivate::FnServiceInfo::FnServiceInfo(std::function<QObject*(QObjectList)> &&creator, QByteArrayList &&injectables, bool weak, ServiceRegistry::DestructionScope scope) :
|
|
ServiceInfo{weak, scope},
|
|
_creator{std::move(creator)},
|
|
_injectables{std::move(injectables)}
|
|
{}
|
|
|
|
QObject *ServiceRegistryPrivate::FnServiceInfo::construct(ServiceRegistryPrivate *d) const
|
|
{
|
|
QObjectList params;
|
|
params.reserve(_injectables.size());
|
|
for(const auto &iid : _injectables) {
|
|
auto ref = d->services.value(iid);
|
|
if(!ref)
|
|
throw ServiceDependencyException(iid);
|
|
params.append(ref->instance(d, iid));
|
|
}
|
|
return _creator(params);
|
|
}
|
|
|
|
|
|
|
|
ServiceRegistryPrivate::MetaServiceInfo::MetaServiceInfo(const QMetaObject *metaObject, bool weak, ServiceRegistry::DestructionScope scope) :
|
|
ServiceInfo{weak, scope},
|
|
_metaObject{metaObject}
|
|
{}
|
|
|
|
QObject *ServiceRegistryPrivate::MetaServiceInfo::construct(ServiceRegistryPrivate *d) const
|
|
{
|
|
return d->constructInjectedLocked(_metaObject, nullptr); //services are created without a parent
|
|
}
|
|
|
|
|
|
|
|
ServiceRegistryPrivate::PluginServiceInfo::PluginServiceInfo(QString &&key, QString &&type, QByteArray &&iid, bool weak, ServiceRegistry::DestructionScope scope) :
|
|
ServiceInfo{weak, scope},
|
|
_key{std::move(key)},
|
|
_type{std::move(type)},
|
|
_iid{std::move(iid)}
|
|
{}
|
|
|
|
const QByteArray &ServiceRegistryPrivate::PluginServiceInfo::iid() const
|
|
{
|
|
return _iid;
|
|
}
|
|
|
|
QObject *ServiceRegistryPrivate::PluginServiceInfo::construct(ServiceRegistryPrivate *d) const
|
|
{
|
|
try {
|
|
QPluginFactoryBase factory{_type, _iid};
|
|
QObject *obj = nullptr;
|
|
if(_key.isEmpty()){
|
|
if(!factory.allKeys().isEmpty())
|
|
obj = factory.plugin(factory.allKeys().value(0));
|
|
} else
|
|
obj = factory.plugin(_key);
|
|
if(!obj) {
|
|
throw ServiceConstructionException{"No plugin of type \"" + _type.toUtf8() +
|
|
"\" found that can provide the interface \"" + _iid +
|
|
(_key.isEmpty() ? QByteArray{"\""} : QByteArray{"\" for the key: " + _key.toUtf8()})};
|
|
}
|
|
d->injectLocked(obj);
|
|
return obj;
|
|
} catch(QPluginLoadException &e) { //convert exception...
|
|
throw ServiceConstructionException{e.what()};
|
|
}
|
|
}
|
|
|
|
// ------------- Exceptions Implementation -------------
|
|
|
|
ServiceExistsException::ServiceExistsException(const QByteArray &iid) :
|
|
_msg("An interface with the type id \"" + iid + "\" has already been registered")
|
|
{}
|
|
|
|
ServiceExistsException::ServiceExistsException(const ServiceExistsException * const other) :
|
|
_msg(other->_msg)
|
|
{}
|
|
|
|
const char *ServiceExistsException::what() const noexcept
|
|
{
|
|
return _msg.constData();
|
|
}
|
|
|
|
void ServiceExistsException::raise() const
|
|
{
|
|
throw (*this);
|
|
}
|
|
|
|
QException *ServiceExistsException::clone() const
|
|
{
|
|
return new ServiceExistsException(this);
|
|
}
|
|
|
|
|
|
|
|
ServiceConstructionException::ServiceConstructionException(const QByteArray &message) :
|
|
_msg(message)
|
|
{}
|
|
|
|
ServiceConstructionException::ServiceConstructionException(const ServiceConstructionException * const other) :
|
|
_msg(other->_msg)
|
|
{}
|
|
|
|
const char *ServiceConstructionException::what() const noexcept
|
|
{
|
|
return _msg.constData();
|
|
}
|
|
|
|
void ServiceConstructionException::raise() const
|
|
{
|
|
throw (*this);
|
|
}
|
|
|
|
QException *ServiceConstructionException::clone() const
|
|
{
|
|
return new ServiceConstructionException(this);
|
|
}
|
|
|
|
|
|
|
|
ServiceDependencyException::ServiceDependencyException(const QByteArray &iid) :
|
|
ServiceConstructionException("Failed to construct missing service: " + iid)
|
|
{}
|
|
|
|
ServiceDependencyException::ServiceDependencyException(const ServiceDependencyException * const other) :
|
|
ServiceConstructionException(other)
|
|
{}
|
|
|
|
void ServiceDependencyException::raise() const
|
|
{
|
|
throw (*this);
|
|
}
|
|
|
|
QException *ServiceDependencyException::clone() const
|
|
{
|
|
return new ServiceDependencyException(this);
|
|
}
|
|
|