6#include "application.h"
9#include "controller_p.h"
10#include "dispatcher.h"
12#include <QMetaClassInfo>
13#include <QRegularExpression>
16using namespace Qt::Literals::StringLiterals;
243 , d_ptr(new ControllerPrivate(this))
247Controller::~Controller()
250 qDeleteAll(d->actionList);
257 return d->pathPrefix;
263 auto it = d->actions.constFind(name);
264 if (it != d->actions.constEnd()) {
267 return d->dispatcher->getAction(name.toString(), d->pathPrefix);
273 return d->actionList;
278 return !qstrcmp(metaObject()->className(), className);
293ControllerPrivate::ControllerPrivate(
Controller *parent)
302 q->setObjectName(QString::fromLatin1(q->metaObject()->className()));
304 dispatcher = _dispatcher;
310 const QMetaObject *meta = q->metaObject();
311 const QString className = QString::fromLatin1(meta->className());
312 q->setObjectName(className);
314 bool namespaceFound =
false;
315 for (
int i = meta->classInfoCount() - 1; i >= 0; --i) {
316 if (qstrcmp(meta->classInfo(i).name(),
"Namespace") == 0) {
317 pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
318 while (pathPrefix.startsWith(u
'/')) {
319 pathPrefix.remove(0, 1);
321 namespaceFound =
true;
326 if (!namespaceFound) {
328 bool lastWasUpper =
true;
330 for (
int i = 0; i < className.length(); ++i) {
331 const QChar c = className.at(i);
332 if (c.isLower() || c.isDigit()) {
333 controlerNS.append(c);
334 lastWasUpper =
false;
335 }
else if (c == u
'_') {
336 controlerNS.append(c);
340 controlerNS.append(u
'/');
343 controlerNS.append(c.toLower());
348 pathPrefix = controlerNS;
351 registerActionMethods(meta, q, app);
354void ControllerPrivate::setupFinished()
358 const ActionList beginList = dispatcher->getActions(QStringLiteral(
"Begin"), pathPrefix);
359 if (!beginList.isEmpty()) {
360 beginAutoList.append(beginList.last());
363 beginAutoList.append(dispatcher->getActions(QStringLiteral(
"Auto"), pathPrefix));
365 const ActionList endList = dispatcher->getActions(QStringLiteral(
"End"), pathPrefix);
366 if (!endList.isEmpty()) {
367 end = endList.last();
370 const auto actions = actionList;
371 for (
Action *action : actions) {
372 action->dispatcherReady(dispatcher, q);
375 q->preFork(qobject_cast<Application *>(q->parent()));
384 int &actionRefCount = c->d_ptr->actionRefCount;
387 const auto beginAutoList = d->beginAutoList;
388 for (
Action *action : beginAutoList) {
389 if (actionRefCount) {
390 c->d_ptr->pendingAsync.enqueue(action);
391 }
else if (!action->dispatch(c)) {
399 if (actionRefCount) {
400 c->d_ptr->pendingAsync.enqueue(c->
action());
408 if (actionRefCount) {
409 c->d_ptr->pendingAsync.enqueue(d->end);
410 }
else if (!d->end->dispatch(c)) {
415 if (actionRefCount) {
416 c->d_ptr->engineRequest->status |= EngineRequest::Async;
422Action *ControllerPrivate::actionClass(
const QVariantHash &args)
424 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
425 const QString actionClass = attributes.value(QStringLiteral(
"ActionClass"));
427 QObject *
object = instantiateClass(actionClass,
"Cutelyst::Action");
429 Action *action = qobject_cast<Cutelyst::Action *>(
object);
433 qCWarning(CUTELYST_CONTROLLER) <<
"ActionClass" << actionClass <<
"is not an ActionClass"
434 <<
object->metaObject()->superClass()->
className();
441Action *ControllerPrivate::createAction(
const QVariantHash &args,
442 const QMetaMethod &method,
446 Action *action = actionClass(args);
451 QStack<Component *> roles = gatherActionRoles(args);
452 for (
int i = 0; i < roles.size(); ++i) {
454 code->
init(app, args);
455 code->setParent(action);
460 action->
setName(args.value(QStringLiteral(
"name")).toString());
461 action->
setReverse(args.value(QStringLiteral(
"reverse")).toString());
467void ControllerPrivate::registerActionMethods(
const QMetaObject *meta,
472 for (
int i = 0; i < meta->methodCount(); ++i) {
473 const QMetaMethod method = meta->method(i);
474 const QByteArray name = method.
name();
479 if (method.isValid() &&
480 (method.methodType() == QMetaMethod::Method ||
481 method.methodType() == QMetaMethod::Slot) &&
482 (method.parameterCount() &&
483 method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
486 QByteArray attributeArray;
487 for (
int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
488 QMetaClassInfo classInfo = meta->classInfo(i2);
489 if (name == classInfo.name()) {
490 attributeArray.append(classInfo.value());
493 ParamsMultiMap attrs = parseAttributes(method, attributeArray, name);
496 if (controller->
ns().isEmpty()) {
497 reverse = QString::fromLatin1(name);
499 reverse = controller->
ns() + QLatin1Char(
'/') + QString::fromLatin1(name);
503 createAction({{QStringLiteral(
"name"), QVariant::fromValue(name)},
504 {QStringLiteral(
"reverse"), QVariant::fromValue(reverse)},
505 {QStringLiteral(
"namespace"), QVariant::fromValue(controller->
ns())},
506 {QStringLiteral(
"attributes"), QVariant::fromValue(attrs)}},
512 actionList.append(action);
517ParamsMultiMap ControllerPrivate::parseAttributes(
const QMetaMethod &method,
518 const QByteArray &str,
519 const QByteArray &name)
522 std::vector<std::pair<QString, QString>> attributes;
529 int size = str.size();
536 if (str.at(pos) ==
':') {
537 int keyStart = ++pos;
540 if (str.at(pos) ==
'(') {
542 int valueStart = ++pos;
544 if (str.at(pos) ==
')') {
547 if (++pos < size && str.at(pos) ==
':') {
551 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
553 }
else if (pos >= size) {
557 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
567 }
else if (str.at(pos) ==
':') {
576 key = QString::fromLatin1(str.mid(keyStart, keyLength));
579 if (!value.isEmpty()) {
580 if ((value.startsWith(u
'\'') && value.endsWith(u
'\'')) ||
581 (value.startsWith(u
'"') && value.endsWith(u
'"'))) {
583 value.remove(value.size() - 1, 1);
588 attributes.emplace_back(std::make_pair(key, value));
594 const static auto digitRE = QRegularExpression(u
"\\D"_s);
598 for (
const auto &pair : attributes) {
599 QString key = pair.first;
600 QString value = pair.second;
601 if (key.compare(u
"Global") == 0) {
602 key = QStringLiteral(
"Path");
603 value = parsePathAttr(QLatin1Char(
'/') + QString::fromLatin1(name));
604 }
else if (key.compare(u
"Local") == 0) {
605 key = QStringLiteral(
"Path");
606 value = parsePathAttr(QString::fromLatin1(name));
607 }
else if (key.compare(u
"Path") == 0) {
608 value = parsePathAttr(value);
609 }
else if (key.compare(u
"Args") == 0) {
610 QString args = value;
611 if (!args.isEmpty()) {
612 value = args.remove(digitRE);
614 }
else if (key.compare(u
"CaptureArgs") == 0) {
615 QString captureArgs = value;
616 value = captureArgs.remove(digitRE);
617 }
else if (key.compare(u
"Chained") == 0) {
618 value = parseChainedAttr(value);
621 ret.insert(key, value);
625 if (!ret.contains(QStringLiteral(
"Args")) && !ret.contains(QStringLiteral(
"CaptureArgs")) &&
626 (ret.contains(QStringLiteral(
"AutoArgs")) ||
627 ret.contains(QStringLiteral(
"AutoCaptureArgs")))) {
628 if (ret.contains(QStringLiteral(
"AutoArgs")) &&
629 ret.contains(QStringLiteral(
"AutoCaptureArgs"))) {
630 qFatal(
"Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed",
633 QString parameterName;
634 if (ret.contains(QStringLiteral(
"AutoArgs"))) {
635 ret.remove(QStringLiteral(
"AutoArgs"));
636 parameterName = QStringLiteral(
"Args");
638 ret.remove(QStringLiteral(
"AutoCaptureArgs"));
639 parameterName = QStringLiteral(
"CaptureArgs");
643 if (!(method.parameterCount() == 2 &&
644 method.parameterType(1) == QMetaType::QStringList)) {
645 int parameterCount = 0;
646 for (
int i2 = 1; i2 < method.parameterCount(); ++i2) {
647 int typeId = method.parameterType(i2);
648 if (typeId == QMetaType::QString) {
652 ret.replace(parameterName, QString::number(parameterCount));
658 if (!ret.contains(QStringLiteral(
"Private")) && method.access() == QMetaMethod::Private) {
659 ret.replace(QStringLiteral(
"Private"), QString());
665QStack<Component *> ControllerPrivate::gatherActionRoles(
const QVariantHash &args)
667 QStack<Component *> roles;
668 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
669 auto doesIt = attributes.constFind(QStringLiteral(
"Does"));
670 while (doesIt != attributes.constEnd() && doesIt.key().compare(u
"Does") == 0) {
672 instantiateClass(doesIt.value(), QByteArrayLiteral(
"Cutelyst::Component"));
674 roles.push(qobject_cast<Component *>(
object));
681QString ControllerPrivate::parsePathAttr(
const QString &value)
683 QString ret = pathPrefix;
684 if (value.startsWith(u
'/')) {
686 }
else if (!value.isEmpty()) {
687 ret = pathPrefix + u
'/' + value;
692QString ControllerPrivate::parseChainedAttr(
const QString &attr)
694 QString ret = QStringLiteral(
"/");
695 if (attr.isEmpty()) {
699 if (attr.compare(u
".") == 0) {
700 ret.append(pathPrefix);
701 }
else if (!attr.startsWith(u
'/')) {
702 if (!pathPrefix.isEmpty()) {
703 ret.append(pathPrefix + u
'/' + attr);
715QObject *ControllerPrivate::instantiateClass(
const QString &name,
const QByteArray &super)
717 QString instanceName = name;
718 if (!instanceName.isEmpty()) {
719 const static QRegularExpression nonWordsRE(u
"\\W"_s);
720 instanceName.remove(nonWordsRE);
722 QMetaType
id = QMetaType::fromName(instanceName.toLatin1().data());
724 if (!instanceName.endsWith(QLatin1Char(
'*'))) {
725 instanceName.append(QLatin1Char(
'*'));
728 id = QMetaType::fromName(instanceName.toLatin1().data());
729 if (!
id.isValid() && !instanceName.startsWith(u
"Cutelyst::")) {
730 instanceName = QLatin1String(
"Cutelyst::") + instanceName;
731 id = QMetaType::fromName(instanceName.toLatin1().data());
736 const QMetaObject *metaObj =
id.metaObject();
738 if (!superIsClassName(metaObj->superClass(), super)) {
739 qCWarning(CUTELYST_CONTROLLER)
740 <<
"Class name" << instanceName <<
"is not a derived class of" << super;
743 QObject *
object = metaObj->newInstance();
745 qCWarning(CUTELYST_CONTROLLER)
746 <<
"Could create a new instance of" << instanceName
747 <<
"make sure it's default constructor is "
748 "marked with the Q_INVOKABLE macro";
754 Component *component = application->createComponentPlugin(name);
759 component = application->createComponentPlugin(instanceName);
766 qCCritical(CUTELYST_CONTROLLER,
767 "Could not create component '%s', you can register it with "
768 "qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
769 qPrintable(instanceName),
770 qPrintable(instanceName));
777bool ControllerPrivate::superIsClassName(
const QMetaObject *super,
const QByteArray &className)
780 if (super->className() == className) {
783 return superIsClassName(super->superClass(), className);
788#include "moc_controller.cpp"
This class represents a Cutelyst Action.
void setupAction(const QVariantHash &args, Application *app)
bool dispatch(Context *c)
void setMethod(const QMetaMethod &method)
QString className() const noexcept
void setController(Controller *controller)
The Cutelyst application.
The Cutelyst Component base class.
void setReverse(const QString &reverse)
virtual bool init(Application *application, const QVariantHash &args)
void applyRoles(const QStack< Component * > &roles)
QString reverse() const noexcept
QString name() const noexcept
void setName(const QString &name)
Cutelyst Controller base class.
virtual bool preFork(Application *app)
virtual bool postFork(Application *app)
bool operator==(const char *className)
QString ns() const noexcept
ActionList actions() const noexcept
bool _DISPATCH(Context *c)
Controller(QObject *parent=nullptr)
Action * actionFor(QStringView name) const
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList