Cutelyst  3.5.0
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 
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 
32 {
33  Q_D(const Controller);
34  return d->pathPrefix;
35 }
36 
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 
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(QLatin1Char('/'))) {
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 == QLatin1Char('_')) {
121  controlerNS.append(c);
122  lastWasUpper = true;
123  } else {
124  if (!lastWasUpper) {
125  controlerNS.append(QLatin1Char('/'));
126  }
127  if (c != QLatin1Char(':')) {
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 &asyncDetached = c->d_ptr->asyncDetached;
170 
171  // Dispatch to Begin and Auto
172  const auto beginAutoList = d->beginAutoList;
173  for (Action *action : beginAutoList) {
174  if (asyncDetached) {
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 (asyncDetached) {
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 (asyncDetached) {
194  c->d_ptr->pendingAsync.append(d->end);
195  } else if (!d->end->dispatch(c)) {
196  ret = false;
197  }
198  }
199 
200  if (asyncDetached) {
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(QLatin1Char('\'')) && value.endsWith(QLatin1Char('\''))) ||
357  (value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"')))) {
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(QLatin1Char('/'))) {
455  ret = value;
456  } else if (!value.isEmpty()) {
457  ret = pathPrefix + QLatin1Char('/') + 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(QLatin1Char('/'))) {
472  if (!pathPrefix.isEmpty()) {
473  ret.append(pathPrefix + QLatin1Char('/') + 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"
void setName(const QString &name)
Definition: component.cpp:37
QString & append(QChar ch)
bool dispatch(Context *c)
Definition: action.h:81
typename QMap< Key, T >::const_iterator constFind(const Key &key, const T &value) const const
QByteArray name() const const
void append(const T &value)
typename QMap< Key, T >::iterator replace(const Key &key, const T &value)
char at(int i) const const
const QMetaObject * metaObjectForType(int type)
void setReverse(const QString &reverse)
Definition: component.cpp:49
const QMetaObject * superClass() const const
void push(const T &t)
bool isDigit() const const
T & last()
virtual const QMetaObject * metaObject() const const
virtual bool preFork(Application *app)
Definition: controller.cpp:68
Action * actionFor(const QString &name) const
Definition: controller.cpp:37
const char * name() const const
QString & remove(int position, int n)
int type(const char *typeName)
QMetaMethod::Access access() const const
The Cutelyst Component base class.
Definition: component.h:25
T value(int i) const const
This class represents a Cutelyst Action.
Definition: action.h:34
The Cutelyst Context.
Definition: context.h:38
QString number(int n, int base)
Cutelyst Controller base class
Definition: controller.h:89
int remove(const Key &key, const T &value)
bool isEmpty() const const
int methodCount() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isValid() const const
int classInfoCount() const const
int parameterCount() const const
const char * value() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
virtual bool init(Application *application, const QVariantHash &args)
Definition: component.cpp:55
typename QMap< Key, T >::iterator insert(const Key &key, const T &value)
bool _DISPATCH(Context *c)
Definition: controller.cpp:163
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void applyRoles(const QStack< Component *> &roles)
Definition: component.cpp:131
QString reverse() const
Definition: component.cpp:43
QByteArray mid(int pos, int len) const const
QMetaClassInfo classInfo(int index) const const
int parameterType(int index) const const
QByteArray & append(char ch)
void setParent(QObject *parent)
const char * className() const const
QVariant fromValue(const T &value)
QChar toLower() const const
void setController(Controller *controller)
Definition: action.cpp:38
virtual bool postFork(Application *app)
Definition: controller.cpp:74
const Key key(const T &value, const Key &defaultKey) const const
bool contains(const Key &key, const T &value) const const
QMetaMethod::MethodType methodType() const const
const T & at(int i) const const
QByteArray toLatin1() const const
QString toString() const const
bool isEmpty() const const
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:44
const QChar at(int position) const const
int length() const const
char * data()
QString fromLatin1(const char *str, int size)
bool operator==(const char *className)
Definition: controller.cpp:63
The Cutelyst Application.
Definition: application.h:42
void setMethod(const QMetaMethod &method)
Definition: action.cpp:25
int size() const const
int size() const const
ActionList actions() const
Definition: controller.cpp:57
int compare(const QString &other, Qt::CaseSensitivity cs) const const
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
QObject * newInstance(QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) const const
QMetaMethod method(int index) const const
bool isLower() const const
QString ns() const
Definition: controller.cpp:31
const T value(const Key &key, const T &defaultValue) const const
Controller(QObject *parent=nullptr)
Definition: controller.cpp:19