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