cutelyst  3.7.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
controller.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "controller_p.h"
6 
7 #include "application.h"
8 #include "dispatcher.h"
9 #include "action.h"
10 #include "common.h"
11 
12 #include "context_p.h"
13 
14 #include <QMetaClassInfo>
15 #include <QRegularExpression>
16 
17 using namespace Cutelyst;
18 
19 Controller::Controller(QObject *parent) : QObject(parent)
20  , d_ptr(new ControllerPrivate(this))
21 {
22 }
23 
24 Controller::~Controller()
25 {
26  Q_D(Controller);
27  qDeleteAll(d->actionList);
28  delete d_ptr;
29 }
30 
31 QString Controller::ns() const
32 {
33  Q_D(const Controller);
34  return d->pathPrefix;
35 }
36 
37 Action *Controller::actionFor(const QString &name) const
38 {
39  Q_D(const Controller);
40  auto it = d->actions.constFind(name);
41  if (it != d->actions.constEnd()) {
42  return it->action;
43  }
44  return d->dispatcher->getAction(name, d->pathPrefix);
45 }
46 
47 Action *Controller::actionFor(QStringView name) const
48 {
49  Q_D(const Controller);
50  auto it = d->actions.constFind(name);
51  if (it != d->actions.constEnd()) {
52  return it->action;
53  }
54  return d->dispatcher->getAction(name.toString(), d->pathPrefix);
55 }
56 
58 {
59  Q_D(const Controller);
60  return d->actionList;
61 }
62 
63 bool Controller::operator==(const char *className)
64 {
65  return !qstrcmp(metaObject()->className(), className);
66 }
67 
69 {
70  Q_UNUSED(app)
71  return true;
72 }
73 
75 {
76  Q_UNUSED(app)
77  return true;
78 }
79 
80 ControllerPrivate::ControllerPrivate(Controller *parent) :
81  q_ptr(parent)
82 {
83 }
84 
85 void ControllerPrivate::init(Application *app, Dispatcher *_dispatcher)
86 {
87  Q_Q(Controller);
88 
89  dispatcher = _dispatcher;
90  application = app;
91 
92  // Application must always be our parent
93  q->setParent(app);
94 
95  const QMetaObject *meta = q->metaObject();
96  const QString className = QString::fromLatin1(meta->className());
97  q->setObjectName(className);
98 
99  bool namespaceFound = false;
100  for (int i = meta->classInfoCount() - 1; i >= 0; --i) {
101  if (qstrcmp(meta->classInfo(i).name(), "Namespace") == 0) {
102  pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
103  while (pathPrefix.startsWith(u'/')) {
104  pathPrefix.remove(0, 1);
105  }
106  namespaceFound = true;
107  break;
108  }
109  }
110 
111  if (!namespaceFound) {
112  QString controlerNS;
113  bool lastWasUpper = true;
114 
115  for (int i = 0; i < className.length(); ++i) {
116  const QChar c = className.at(i);
117  if (c.isLower() || c.isDigit()) {
118  controlerNS.append(c);
119  lastWasUpper = false;
120  } else if (c == u'_') {
121  controlerNS.append(c);
122  lastWasUpper = true;
123  } else {
124  if (!lastWasUpper) {
125  controlerNS.append(u'/');
126  }
127  if (c != u':') {
128  controlerNS.append(c.toLower());
129  }
130  lastWasUpper = true;
131  }
132  }
133  pathPrefix = controlerNS;
134  }
135 
136  registerActionMethods(meta, q, app);
137 }
138 
139 void ControllerPrivate::setupFinished()
140 {
141  Q_Q(Controller);
142 
143  const ActionList beginList = dispatcher->getActions(QStringLiteral("Begin"), pathPrefix);
144  if (!beginList.isEmpty()) {
145  beginAutoList.append(beginList.last());
146  }
147 
148  beginAutoList.append(dispatcher->getActions(QStringLiteral("Auto"), pathPrefix));
149 
150  const ActionList endList = dispatcher->getActions(QStringLiteral("End"), pathPrefix);
151  if (!endList.isEmpty()) {
152  end = endList.last();
153  }
154 
155  const auto actions = actionList;
156  for (Action *action : actions) {
157  action->dispatcherReady(dispatcher, q);
158  }
159 
160  q->preFork(qobject_cast<Application *>(q->parent()));
161 }
162 
164 {
165  Q_D(Controller);
166 
167  bool ret = true;
168 
169  int &actionRefCount = c->d_ptr->actionRefCount;
170 
171  // Dispatch to Begin and Auto
172  const auto beginAutoList = d->beginAutoList;
173  for (Action *action : beginAutoList) {
174  if (actionRefCount) {
175  c->d_ptr->pendingAsync.append(action);
176  } else if (!action->dispatch(c)) {
177  ret = false;
178  break;
179  }
180  }
181 
182  // Dispatch to Action
183  if (ret) {
184  if (actionRefCount) {
185  c->d_ptr->pendingAsync.append(c->action());
186  } else {
187  ret = c->action()->dispatch(c);
188  }
189  }
190 
191  // Dispatch to End
192  if (d->end) {
193  if (actionRefCount) {
194  c->d_ptr->pendingAsync.append(d->end);
195  } else if (!d->end->dispatch(c)) {
196  ret = false;
197  }
198  }
199 
200  if (actionRefCount) {
201  c->d_ptr->engineRequest->status |= EngineRequest::Async;
202  }
203 
204  return ret;
205 }
206 
207 Action *ControllerPrivate::actionClass(const QVariantHash &args)
208 {
209  const auto attributes = args.value(QStringLiteral("attributes")).value<ParamsMultiMap>();
210  const QString actionClass = attributes.value(QStringLiteral("ActionClass"));
211 
212  QObject *object = instantiateClass(actionClass, "Cutelyst::Action");
213  if (object) {
214  Action *action = qobject_cast<Action*>(object);
215  if (action) {
216  return action;
217  }
218  qCWarning(CUTELYST_CONTROLLER) << "ActionClass"
219  << actionClass
220  << "is not an ActionClass";
221  delete object;
222  }
223 
224  return new Action;
225 }
226 
227 Action *ControllerPrivate::createAction(const QVariantHash &args, const QMetaMethod &method, Controller *controller, Application *app)
228 {
229  Action *action = actionClass(args);
230  if (!action) {
231  return nullptr;
232  }
233 
234  QStack<Component *> roles = gatherActionRoles(args);
235  for (int i = 0; i < roles.size(); ++i) {
236  Component *code = roles.at(i);
237  code->init(app, args);
238  code->setParent(action);
239  }
240  action->applyRoles(roles);
241  action->setMethod(method);
242  action->setController(controller);
243  action->setName(args.value(QStringLiteral("name")).toString());
244  action->setReverse(args.value(QStringLiteral("reverse")).toString());
245  action->setupAction(args, app);
246 
247  return action;
248 }
249 
250 void ControllerPrivate::registerActionMethods(const QMetaObject *meta, Controller *controller, Application *app)
251 {
252  // Setup actions
253  for (int i = 0; i < meta->methodCount(); ++i) {
254  const QMetaMethod method = meta->method(i);
255  const QByteArray name = method.name();
256 
257  // We register actions that are either a Q_SLOT
258  // or a Q_INVOKABLE function which has the first
259  // parameter type equal to Context*
260  if (method.isValid() &&
261  (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot) &&
262  (method.parameterCount() && method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
263 
264  // Build up the list of attributes for the class info
265  QByteArray attributeArray;
266  for (int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
267  QMetaClassInfo classInfo = meta->classInfo(i2);
268  if (name == classInfo.name()) {
269  attributeArray.append(classInfo.value());
270  }
271  }
272  ParamsMultiMap attrs = parseAttributes(method, attributeArray, name);
273 
274  QString reverse;
275  if (controller->ns().isEmpty()) {
276  reverse = QString::fromLatin1(name);
277  } else {
278  reverse = controller->ns() + QLatin1Char('/') + QString::fromLatin1(name);
279  }
280 
281  Action *action = createAction({
282  {QStringLiteral("name"), QVariant::fromValue(name)},
283  {QStringLiteral("reverse"), QVariant::fromValue(reverse)},
284  {QStringLiteral("namespace"), QVariant::fromValue(controller->ns())},
285  {QStringLiteral("attributes"), QVariant::fromValue(attrs)}
286  },
287  method,
288  controller,
289  app);
290 
291  actions.insert(action->reverse(), { action->reverse(), action });
292  actionList.append(action);
293  }
294  }
295 }
296 
297 ParamsMultiMap ControllerPrivate::parseAttributes(const QMetaMethod &method, const QByteArray &str, const QByteArray &name)
298 {
299  ParamsMultiMap ret;
300  std::vector<std::pair<QString, QString> > attributes;
301  // This is probably not the best parser ever
302  // but it handles cases like:
303  // :Args:Local('fo"')o'):ActionClass('foo')
304  // into
305  // (("Args",""), ("Local","'fo"')o'"), ("ActionClass","'foo'"))
306 
307  int size = str.size();
308  int pos = 0;
309  while (pos < size) {
310  QString key;
311  QString value;
312 
313  // find the start of a key
314  if (str.at(pos) == ':') {
315  int keyStart = ++pos;
316  int keyLength = 0;
317  while (pos < size) {
318  if (str.at(pos) == '(') {
319  // attribute has value
320  int valueStart = ++pos;
321  while (pos < size) {
322  if (str.at(pos) == ')') {
323  // found the possible end of the value
324  int valueEnd = pos;
325  if (++pos < size && str.at(pos) == ':') {
326  // found the start of a key so this is
327  // really the end of a value
328  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
329  break;
330  } else if (pos >= size) {
331  // found the end of the string
332  // save the remainig as the value
333  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
334  break;
335  }
336  // string was not '):' or ')$'
337  continue;
338  }
339  ++pos;
340  }
341 
342  break;
343  } else if (str.at(pos) == ':') {
344  // Attribute has no value
345  break;
346  }
347  ++keyLength;
348  ++pos;
349  }
350 
351  // stopre the key
352  key = QString::fromLatin1(str.mid(keyStart, keyLength));
353 
354  // remove quotes
355  if (!value.isEmpty()) {
356  if ((value.startsWith(u'\'') && value.endsWith(u'\'')) ||
357  (value.startsWith(u'"') && value.endsWith(u'"'))) {
358  value.remove(0, 1);
359  value.remove(value.size() - 1, 1);
360  }
361  }
362 
363  // store the key/value pair found
364  attributes.emplace_back(std::make_pair(key, value));
365  continue;
366  }
367  ++pos;
368  }
369 
370  // Add the attributes to the map in the reverse order so
371  // that values() return them in the right order
372  for (const auto &pair : attributes) {
373  QString key = pair.first;
374  QString value = pair.second;
375  if (key.compare(u"Global") == 0) {
376  key = QStringLiteral("Path");
377  value = parsePathAttr(QLatin1Char('/') + QString::fromLatin1(name));
378  } else if (key.compare(u"Local") == 0) {
379  key = QStringLiteral("Path");
380  value = parsePathAttr(QString::fromLatin1(name));
381  } else if (key.compare(u"Path") == 0) {
382  value = parsePathAttr(value);
383  } else if (key.compare(u"Args") == 0) {
384  QString args = value;
385  if (!args.isEmpty()) {
386  value = args.remove(QRegularExpression(QStringLiteral("\\D")));
387  }
388  } else if (key.compare(u"CaptureArgs") == 0) {
389  QString captureArgs = value;
390  value = captureArgs.remove(QRegularExpression(QStringLiteral("\\D")));
391  } else if (key.compare(u"Chained") == 0) {
392  value = parseChainedAttr(value);
393  }
394 
395  ret.insert(key, value);
396  }
397 
398  // Handle special AutoArgs and AutoCaptureArgs case
399  if (!ret.contains(QStringLiteral("Args")) && !ret.contains(QStringLiteral("CaptureArgs")) &&
400  (ret.contains(QStringLiteral("AutoArgs")) || ret.contains(QStringLiteral("AutoCaptureArgs")))) {
401  if (ret.contains(QStringLiteral("AutoArgs")) && ret.contains(QStringLiteral("AutoCaptureArgs"))) {
402  qFatal("Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed", name.constData());
403  } else {
404  QString parameterName;
405  if (ret.contains(QStringLiteral("AutoArgs"))) {
406  ret.remove(QStringLiteral("AutoArgs"));
407  parameterName = QStringLiteral("Args");
408  } else {
409  ret.remove(QStringLiteral("AutoCaptureArgs"));
410  parameterName = QStringLiteral("CaptureArgs");
411  }
412 
413  // If the signature is not QStringList we count them
414  if (!(method.parameterCount() == 2 && method.parameterType(1) == QMetaType::QStringList)) {
415  int parameterCount = 0;
416  for (int i2 = 1; i2 < method.parameterCount(); ++i2) {
417  int typeId = method.parameterType(i2);
418  if (typeId == QMetaType::QString) {
419  ++parameterCount;
420  }
421  }
422  ret.replace(parameterName, QString::number(parameterCount));
423  }
424  }
425 
426  }
427 
428  // If the method is private add a Private attribute
429  if (!ret.contains(QStringLiteral("Private")) && method.access() == QMetaMethod::Private) {
430  ret.replace(QStringLiteral("Private"), QString());
431  }
432 
433  return ret;
434 }
435 
436 QStack<Component *> ControllerPrivate::gatherActionRoles(const QVariantHash &args)
437 {
438  QStack<Component *> roles;
439  const auto attributes = args.value(QStringLiteral("attributes")).value<ParamsMultiMap>();
440  auto doesIt = attributes.constFind(QStringLiteral("Does"));
441  while (doesIt != attributes.constEnd() && doesIt.key().compare(u"Does") == 0) {
442  QObject *object = instantiateClass(doesIt.value(), QByteArrayLiteral("Cutelyst::Component"));
443  if (object) {
444  roles.push(qobject_cast<Component *>(object));
445  }
446  ++doesIt;
447  }
448  return roles;
449 }
450 
451 QString ControllerPrivate::parsePathAttr(const QString &value)
452 {
453  QString ret = pathPrefix;
454  if (value.startsWith(u'/')) {
455  ret = value;
456  } else if (!value.isEmpty()) {
457  ret = pathPrefix + u'/' + value;
458  }
459  return ret;
460 }
461 
462 QString ControllerPrivate::parseChainedAttr(const QString &attr)
463 {
464  QString ret = QStringLiteral("/");
465  if (attr.isEmpty()) {
466  return ret;
467  }
468 
469  if (attr.compare(u".") == 0) {
470  ret.append(pathPrefix);
471  } else if (!attr.startsWith(u'/')) {
472  if (!pathPrefix.isEmpty()) {
473  ret.append(pathPrefix + u'/' + attr);
474  } else {
475  // special case namespace '' (root)
476  ret.append(attr);
477  }
478  } else {
479  ret = attr;
480  }
481 
482  return ret;
483 }
484 
485 QObject *ControllerPrivate::instantiateClass(const QString &name, const QByteArray &super)
486 {
487  QString instanceName = name;
488  if (!instanceName.isEmpty()) {
489  instanceName.remove(QRegularExpression(QStringLiteral("\\W")));
490 
491 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
492  QMetaType id = QMetaType::fromName(instanceName.toLatin1().data());
493  if (!id.isValid()) {
494  if (!instanceName.endsWith(QLatin1Char('*'))) {
495  instanceName.append(QLatin1Char('*'));
496  }
497 
498  id = QMetaType::fromName(instanceName.toLatin1().data());
499  if (!id.isValid() && !instanceName.startsWith(u"Cutelyst::")) {
500  instanceName = QLatin1String("Cutelyst::") + instanceName;
501  id = QMetaType::fromName(instanceName.toLatin1().data());
502  }
503  }
504 
505  if (id.isValid()) {
506  const QMetaObject *metaObj = id.metaObject();
507  if (metaObj) {
508  if (!superIsClassName(metaObj->superClass(), super)) {
509  qCWarning(CUTELYST_CONTROLLER)
510  << "Class name"
511  << instanceName
512  << "is not a derived class of"
513  << super;
514  }
515 
516  QObject *object = metaObj->newInstance();
517  if (!object) {
518  qCWarning(CUTELYST_CONTROLLER)
519  << "Could create a new instance of"
520  << instanceName
521  << "make sure it's default constructor is "
522  "marked with the Q_INVOKABLE macro";
523  }
524 
525  return object;
526  }
527  } else {
528  Component *component = application->createComponentPlugin(name);
529  if (component) {
530  return component;
531  }
532 
533  component = application->createComponentPlugin(instanceName);
534  if (component) {
535  return component;
536  }
537  }
538 
539  if (!id.isValid()) {
540  qCCritical(CUTELYST_CONTROLLER,
541  "Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
542  qPrintable(instanceName), qPrintable(instanceName));
543  }
544  }
545 #else
546  int id = QMetaType::type(instanceName.toLatin1().data());
547  if (!id) {
548  if (!instanceName.endsWith(QLatin1Char('*'))) {
549  instanceName.append(QLatin1Char('*'));
550  }
551 
552  id = QMetaType::type(instanceName.toLatin1().data());
553  if (!id && !instanceName.startsWith(u"Cutelyst::")) {
554  instanceName = QLatin1String("Cutelyst::") + instanceName;
555  id = QMetaType::type(instanceName.toLatin1().data());
556  }
557  }
558 
559  if (id) {
560  const QMetaObject *metaObj = QMetaType::metaObjectForType(id);
561  if (metaObj) {
562  if (!superIsClassName(metaObj->superClass(), super)) {
563  qCWarning(CUTELYST_CONTROLLER)
564  << "Class name"
565  << instanceName
566  << "is not a derived class of"
567  << super;
568  }
569 
570  QObject *object = metaObj->newInstance();
571  if (!object) {
572  qCWarning(CUTELYST_CONTROLLER)
573  << "Could create a new instance of"
574  << instanceName
575  << "make sure it's default constructor is "
576  "marked with the Q_INVOKABLE macro";
577  }
578 
579  return object;
580  }
581  } else {
582  Component *component = application->createComponentPlugin(name);
583  if (component) {
584  return component;
585  }
586 
587  component = application->createComponentPlugin(instanceName);
588  if (component) {
589  return component;
590  }
591  }
592 
593  if (!id) {
594  qCCritical(CUTELYST_CONTROLLER,
595  "Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
596  qPrintable(instanceName), qPrintable(instanceName));
597  }
598  }
599 #endif
600  return nullptr;
601 }
602 
603 bool ControllerPrivate::superIsClassName(const QMetaObject *super, const QByteArray &className)
604 {
605  if (super) {
606  if (super->className() == className) {
607  return true;
608  }
609  return superIsClassName(super->superClass(), className);
610  }
611  return false;
612 }
613 
614 #include "moc_controller.cpp"
This class represents a Cutelyst Action.
Definition: action.h:35
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:44
bool dispatch(Context *c)
Definition: action.h:81
void setMethod(const QMetaMethod &method)
Definition: action.cpp:25
void setController(Controller *controller)
Definition: action.cpp:38
The Cutelyst Application.
Definition: application.h:43
The Cutelyst Component base class.
Definition: component.h:26
void setReverse(const QString &reverse)
Definition: component.cpp:49
virtual bool init(Application *application, const QVariantHash &args)
Definition: component.cpp:55
void applyRoles(const QStack< Component * > &roles)
Definition: component.cpp:131
QString name() const
Definition: component.cpp:31
void setName(const QString &name)
Definition: component.cpp:37
QString reverse() const
Definition: component.cpp:43
The Cutelyst Context.
Definition: context.h:39
Cutelyst Controller base class
Definition: controller.h:90
virtual bool preFork(Application *app)
Definition: controller.cpp:68
Action * actionFor(const QString &name) const
Definition: controller.cpp:37
ActionList actions() const
Definition: controller.cpp:57
virtual bool postFork(Application *app)
Definition: controller.cpp:74
bool operator==(const char *className)
Definition: controller.cpp:63
QString ns() const
Definition: controller.cpp:31
bool _DISPATCH(Context *c)
Definition: controller.cpp:163
Controller(QObject *parent=nullptr)
Definition: controller.cpp:19
The Cutelyst Dispatcher.
Definition: dispatcher.h:28
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QVector< Action * > ActionList
Definition: action.h:153
QMultiMap< QString, QString > ParamsMultiMap