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