6#include "application.h"
9#include "controller_p.h"
10#include "dispatcher.h"
12#include <QMetaClassInfo>
13#include <QRegularExpression>
19 , d_ptr(new ControllerPrivate(this))
23Controller::~Controller()
26 qDeleteAll(d->actionList);
39 auto it = d->actions.constFind(name);
40 if (it != d->actions.constEnd()) {
43 return d->dispatcher->getAction(name.toString(), d->pathPrefix);
54 return !qstrcmp(metaObject()->className(), className);
69ControllerPrivate::ControllerPrivate(
Controller *parent)
78 q->setObjectName(QString::fromLatin1(q->metaObject()->className()));
80 dispatcher = _dispatcher;
86 const QMetaObject *meta = q->metaObject();
87 const QString className = QString::fromLatin1(meta->className());
88 q->setObjectName(className);
90 bool namespaceFound =
false;
91 for (
int i = meta->classInfoCount() - 1; i >= 0; --i) {
92 if (qstrcmp(meta->classInfo(i).name(),
"Namespace") == 0) {
93 pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
94 while (pathPrefix.startsWith(u
'/')) {
95 pathPrefix.remove(0, 1);
97 namespaceFound =
true;
102 if (!namespaceFound) {
104 bool lastWasUpper =
true;
106 for (
int i = 0; i < className.length(); ++i) {
107 const QChar c = className.at(i);
108 if (c.isLower() || c.isDigit()) {
109 controlerNS.append(c);
110 lastWasUpper =
false;
111 }
else if (c == u
'_') {
112 controlerNS.append(c);
116 controlerNS.append(u
'/');
119 controlerNS.append(c.toLower());
124 pathPrefix = controlerNS;
127 registerActionMethods(meta, q, app);
130void ControllerPrivate::setupFinished()
134 const ActionList beginList = dispatcher->getActions(QStringLiteral(
"Begin"), pathPrefix);
135 if (!beginList.isEmpty()) {
136 beginAutoList.append(beginList.last());
139 beginAutoList.append(dispatcher->getActions(QStringLiteral(
"Auto"), pathPrefix));
141 const ActionList endList = dispatcher->getActions(QStringLiteral(
"End"), pathPrefix);
142 if (!endList.isEmpty()) {
143 end = endList.last();
146 const auto actions = actionList;
147 for (
Action *action : actions) {
148 action->dispatcherReady(dispatcher, q);
151 q->preFork(qobject_cast<Application *>(q->parent()));
160 int &actionRefCount = c->d_ptr->actionRefCount;
163 const auto beginAutoList = d->beginAutoList;
164 for (
Action *action : beginAutoList) {
165 if (actionRefCount) {
166 c->d_ptr->pendingAsync.append(action);
167 }
else if (!action->dispatch(c)) {
175 if (actionRefCount) {
176 c->d_ptr->pendingAsync.append(c->action());
184 if (actionRefCount) {
185 c->d_ptr->pendingAsync.append(d->end);
186 }
else if (!d->end->dispatch(c)) {
191 if (actionRefCount) {
192 c->d_ptr->engineRequest->status |= EngineRequest::Async;
198Action *ControllerPrivate::actionClass(
const QVariantHash &args)
200 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
201 const QString actionClass = attributes.value(QStringLiteral(
"ActionClass"));
203 QObject *
object = instantiateClass(actionClass,
"Cutelyst::Action");
205 Action *action = qobject_cast<Action *>(
object);
209 qCWarning(CUTELYST_CONTROLLER) <<
"ActionClass" << actionClass <<
"is not an ActionClass";
216Action *ControllerPrivate::createAction(
const QVariantHash &args,
217 const QMetaMethod &method,
221 Action *action = actionClass(args);
226 QStack<Component *> roles = gatherActionRoles(args);
227 for (
int i = 0; i < roles.size(); ++i) {
229 code->
init(app, args);
230 code->setParent(action);
235 action->
setName(args.value(QStringLiteral(
"name")).toString());
236 action->
setReverse(args.value(QStringLiteral(
"reverse")).toString());
242void ControllerPrivate::registerActionMethods(
const QMetaObject *meta,
247 for (
int i = 0; i < meta->methodCount(); ++i) {
248 const QMetaMethod method = meta->method(i);
249 const QByteArray name = method.
name();
254 if (method.isValid() &&
255 (method.methodType() == QMetaMethod::Method ||
256 method.methodType() == QMetaMethod::Slot) &&
257 (method.parameterCount() &&
258 method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
261 QByteArray attributeArray;
262 for (
int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
263 QMetaClassInfo classInfo = meta->classInfo(i2);
264 if (name == classInfo.name()) {
265 attributeArray.append(classInfo.value());
268 ParamsMultiMap attrs = parseAttributes(method, attributeArray, name);
271 if (controller->
ns().isEmpty()) {
272 reverse = QString::fromLatin1(name);
274 reverse = controller->
ns() + QLatin1Char(
'/') + QString::fromLatin1(name);
278 createAction({{QStringLiteral(
"name"), QVariant::fromValue(name)},
279 {QStringLiteral(
"reverse"), QVariant::fromValue(reverse)},
280 {QStringLiteral(
"namespace"), QVariant::fromValue(controller->
ns())},
281 {QStringLiteral(
"attributes"), QVariant::fromValue(attrs)}},
287 actionList.append(action);
292ParamsMultiMap ControllerPrivate::parseAttributes(
const QMetaMethod &method,
293 const QByteArray &str,
294 const QByteArray &name)
297 std::vector<std::pair<QString, QString>> attributes;
304 int size = str.size();
311 if (str.at(pos) ==
':') {
312 int keyStart = ++pos;
315 if (str.at(pos) ==
'(') {
317 int valueStart = ++pos;
319 if (str.at(pos) ==
')') {
322 if (++pos < size && str.at(pos) ==
':') {
326 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
328 }
else if (pos >= size) {
332 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
342 }
else if (str.at(pos) ==
':') {
351 key = QString::fromLatin1(str.mid(keyStart, keyLength));
354 if (!value.isEmpty()) {
355 if ((value.startsWith(u
'\'') && value.endsWith(u
'\'')) ||
356 (value.startsWith(u
'"') && value.endsWith(u
'"'))) {
358 value.remove(value.size() - 1, 1);
363 attributes.emplace_back(std::make_pair(key, value));
369 const static auto digitRE = QRegularExpression(u
"\\D"_qs);
373 for (
const auto &pair : attributes) {
374 QString key = pair.first;
375 QString value = pair.second;
376 if (key.compare(u
"Global") == 0) {
377 key = QStringLiteral(
"Path");
378 value = parsePathAttr(QLatin1Char(
'/') + QString::fromLatin1(name));
379 }
else if (key.compare(u
"Local") == 0) {
380 key = QStringLiteral(
"Path");
381 value = parsePathAttr(QString::fromLatin1(name));
382 }
else if (key.compare(u
"Path") == 0) {
383 value = parsePathAttr(value);
384 }
else if (key.compare(u
"Args") == 0) {
385 QString args = value;
386 if (!args.isEmpty()) {
387 value = args.remove(digitRE);
389 }
else if (key.compare(u
"CaptureArgs") == 0) {
390 QString captureArgs = value;
391 value = captureArgs.remove(digitRE);
392 }
else if (key.compare(u
"Chained") == 0) {
393 value = parseChainedAttr(value);
396 ret.insert(key, value);
400 if (!ret.contains(QStringLiteral(
"Args")) && !ret.contains(QStringLiteral(
"CaptureArgs")) &&
401 (ret.contains(QStringLiteral(
"AutoArgs")) ||
402 ret.contains(QStringLiteral(
"AutoCaptureArgs")))) {
403 if (ret.contains(QStringLiteral(
"AutoArgs")) &&
404 ret.contains(QStringLiteral(
"AutoCaptureArgs"))) {
405 qFatal(
"Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed",
408 QString parameterName;
409 if (ret.contains(QStringLiteral(
"AutoArgs"))) {
410 ret.remove(QStringLiteral(
"AutoArgs"));
411 parameterName = QStringLiteral(
"Args");
413 ret.remove(QStringLiteral(
"AutoCaptureArgs"));
414 parameterName = QStringLiteral(
"CaptureArgs");
418 if (!(method.parameterCount() == 2 &&
419 method.parameterType(1) == QMetaType::QStringList)) {
420 int parameterCount = 0;
421 for (
int i2 = 1; i2 < method.parameterCount(); ++i2) {
422 int typeId = method.parameterType(i2);
423 if (typeId == QMetaType::QString) {
427 ret.replace(parameterName, QString::number(parameterCount));
433 if (!ret.contains(QStringLiteral(
"Private")) && method.access() == QMetaMethod::Private) {
434 ret.replace(QStringLiteral(
"Private"), QString());
440QStack<Component *> ControllerPrivate::gatherActionRoles(
const QVariantHash &args)
442 QStack<Component *> roles;
443 const auto attributes = args.value(QStringLiteral(
"attributes")).value<
ParamsMultiMap>();
444 auto doesIt = attributes.constFind(QStringLiteral(
"Does"));
445 while (doesIt != attributes.constEnd() && doesIt.key().compare(u
"Does") == 0) {
447 instantiateClass(doesIt.value(), QByteArrayLiteral(
"Cutelyst::Component"));
449 roles.push(qobject_cast<Component *>(
object));
456QString ControllerPrivate::parsePathAttr(
const QString &value)
458 QString ret = pathPrefix;
459 if (value.startsWith(u
'/')) {
461 }
else if (!value.isEmpty()) {
462 ret = pathPrefix + u
'/' + value;
467QString ControllerPrivate::parseChainedAttr(
const QString &attr)
469 QString ret = QStringLiteral(
"/");
470 if (attr.isEmpty()) {
474 if (attr.compare(u
".") == 0) {
475 ret.append(pathPrefix);
476 }
else if (!attr.startsWith(u
'/')) {
477 if (!pathPrefix.isEmpty()) {
478 ret.append(pathPrefix + u
'/' + attr);
490QObject *ControllerPrivate::instantiateClass(
const QString &name,
const QByteArray &super)
492 QString instanceName = name;
493 if (!instanceName.isEmpty()) {
494 instanceName.remove(QRegularExpression(QStringLiteral(
"\\W")));
496 QMetaType
id = QMetaType::fromName(instanceName.toLatin1().data());
498 if (!instanceName.endsWith(QLatin1Char(
'*'))) {
499 instanceName.append(QLatin1Char(
'*'));
502 id = QMetaType::fromName(instanceName.toLatin1().data());
503 if (!
id.isValid() && !instanceName.startsWith(u
"Cutelyst::")) {
504 instanceName = QLatin1String(
"Cutelyst::") + instanceName;
505 id = QMetaType::fromName(instanceName.toLatin1().data());
510 const QMetaObject *metaObj =
id.metaObject();
512 if (!superIsClassName(metaObj->superClass(), super)) {
513 qCWarning(CUTELYST_CONTROLLER)
514 <<
"Class name" << instanceName <<
"is not a derived class of" << super;
517 QObject *
object = metaObj->newInstance();
519 qCWarning(CUTELYST_CONTROLLER)
520 <<
"Could create a new instance of" << instanceName
521 <<
"make sure it's default constructor is "
522 "marked with the Q_INVOKABLE macro";
528 Component *component = application->createComponentPlugin(name);
533 component = application->createComponentPlugin(instanceName);
540 qCCritical(CUTELYST_CONTROLLER,
541 "Could not create component '%s', you can register it with "
542 "qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
543 qPrintable(instanceName),
544 qPrintable(instanceName));
551bool ControllerPrivate::superIsClassName(
const QMetaObject *super,
const QByteArray &className)
554 if (super->className() == className) {
557 return superIsClassName(super->superClass(), className);
562#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)
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
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList
QMultiMap< QString, QString > ParamsMultiMap