Cutelyst  3.1.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 if (!c->action()->dispatch(c)) {
190  ret = false;
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.push_back({ 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  auto i = attributes.crbegin();
376  const auto end = attributes.crend();
377  while (i != end) {
378  QString key = i->first;
379  QString value = i->second;
380  if (key == QLatin1String("Global")) {
381  key = QStringLiteral("Path");
382  value = parsePathAttr(QLatin1Char('/') + QString::fromLatin1(name));
383  } else if (key == QLatin1String("Local")) {
384  key = QStringLiteral("Path");
385  value = parsePathAttr(QString::fromLatin1(name));
386  } else if (key == QLatin1String("Path")) {
387  value = parsePathAttr(value);
388  } else if (key == QLatin1String("Args")) {
389  QString args = value;
390  if (!args.isEmpty()) {
391  value = args.remove(QRegularExpression(QStringLiteral("\\D")));
392  }
393  } else if (key == QLatin1String("CaptureArgs")) {
394  QString captureArgs = value;
395  value = captureArgs.remove(QRegularExpression(QStringLiteral("\\D")));
396  } else if (key == QLatin1String("Chained")) {
397  value = parseChainedAttr(value);
398  }
399 
400  ret.insert(key, value);
401  ++i;
402  }
403 
404  // Handle special AutoArgs and AutoCaptureArgs case
405  if (!ret.contains(QStringLiteral("Args")) && !ret.contains(QStringLiteral("CaptureArgs")) &&
406  (ret.contains(QStringLiteral("AutoArgs")) || ret.contains(QStringLiteral("AutoCaptureArgs")))) {
407  if (ret.contains(QStringLiteral("AutoArgs")) && ret.contains(QStringLiteral("AutoCaptureArgs"))) {
408  qFatal("Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed", name.constData());
409  } else {
410  QString parameterName;
411  if (ret.contains(QStringLiteral("AutoArgs"))) {
412  ret.remove(QStringLiteral("AutoArgs"));
413  parameterName = QStringLiteral("Args");
414  } else {
415  ret.remove(QStringLiteral("AutoCaptureArgs"));
416  parameterName = QStringLiteral("CaptureArgs");
417  }
418 
419  // If the signature is not QStringList we count them
420  if (!(method.parameterCount() == 2 && method.parameterType(1) == QMetaType::QStringList)) {
421  int parameterCount = 0;
422  for (int i2 = 1; i2 < method.parameterCount(); ++i2) {
423  int typeId = method.parameterType(i2);
424  if (typeId == QMetaType::QString) {
425  ++parameterCount;
426  }
427  }
428  ret.replace(parameterName, QString::number(parameterCount));
429  }
430  }
431 
432  }
433 
434  // If the method is private add a Private attribute
435  if (!ret.contains(QStringLiteral("Private")) && method.access() == QMetaMethod::Private) {
436  ret.replace(QStringLiteral("Private"), QString());
437  }
438 
439  return ret;
440 }
441 
442 QStack<Component *> ControllerPrivate::gatherActionRoles(const QVariantHash &args)
443 {
444  QStack<Component *> roles;
445  const auto attributes = args.value(QStringLiteral("attributes")).value<ParamsMultiMap>();
446  auto doesIt = attributes.constFind(QStringLiteral("Does"));
447  while (doesIt != attributes.constEnd() && doesIt.key() == QLatin1String("Does")) {
448  QObject *object = instantiateClass(doesIt.value(), QByteArrayLiteral("Cutelyst::Component"));
449  if (object) {
450  roles.push(qobject_cast<Component *>(object));
451  }
452  ++doesIt;
453  }
454  return roles;
455 }
456 
457 QString ControllerPrivate::parsePathAttr(const QString &value)
458 {
459  QString ret = pathPrefix;
460  if (value.startsWith(QLatin1Char('/'))) {
461  ret = value;
462  } else if (!value.isEmpty()) {
463  ret = pathPrefix + QLatin1Char('/') + value;
464  }
465  return ret;
466 }
467 
468 QString ControllerPrivate::parseChainedAttr(const QString &attr)
469 {
470  QString ret = QStringLiteral("/");
471  if (attr.isEmpty()) {
472  return ret;
473  }
474 
475  if (attr == QStringLiteral(".")) {
476  ret.append(pathPrefix);
477  } else if (!attr.startsWith(QLatin1Char('/'))) {
478  if (!pathPrefix.isEmpty()) {
479  ret.append(pathPrefix + QLatin1Char('/') + attr);
480  } else {
481  // special case namespace '' (root)
482  ret.append(attr);
483  }
484  } else {
485  ret = attr;
486  }
487 
488  return ret;
489 }
490 
491 QObject *ControllerPrivate::instantiateClass(const QString &name, const QByteArray &super)
492 {
493  QString instanceName = name;
494  if (!instanceName.isEmpty()) {
495  instanceName.remove(QRegularExpression(QStringLiteral("\\W")));
496 
497 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
498  QMetaType id = QMetaType::fromName(instanceName.toLatin1().data());
499  if (!id.isValid()) {
500  if (!instanceName.endsWith(QLatin1Char('*'))) {
501  instanceName.append(QLatin1Char('*'));
502  }
503 
504  id = QMetaType::fromName(instanceName.toLatin1().data());
505  if (!id.isValid() && !instanceName.startsWith(QStringLiteral("Cutelyst::"))) {
506  instanceName = QLatin1String("Cutelyst::") + instanceName;
507  id = QMetaType::fromName(instanceName.toLatin1().data());
508  }
509  }
510 
511  if (id.isValid()) {
512  const QMetaObject *metaObj = id.metaObject();
513  if (metaObj) {
514  if (!superIsClassName(metaObj->superClass(), super)) {
515  qCWarning(CUTELYST_CONTROLLER)
516  << "Class name"
517  << instanceName
518  << "is not a derived class of"
519  << super;
520  }
521 
522  QObject *object = metaObj->newInstance();
523  if (!object) {
524  qCWarning(CUTELYST_CONTROLLER)
525  << "Could create a new instance of"
526  << instanceName
527  << "make sure it's default constructor is "
528  "marked with the Q_INVOKABLE macro";
529  }
530 
531  return object;
532  }
533  } else {
534  Component *component = application->createComponentPlugin(name);
535  if (component) {
536  return component;
537  }
538 
539  component = application->createComponentPlugin(instanceName);
540  if (component) {
541  return component;
542  }
543  }
544 
545  if (!id.isValid()) {
546  qFatal("Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
547  qPrintable(instanceName), qPrintable(instanceName));
548  }
549  }
550 #else
551  int id = QMetaType::type(instanceName.toLatin1().data());
552  if (!id) {
553  if (!instanceName.endsWith(QLatin1Char('*'))) {
554  instanceName.append(QLatin1Char('*'));
555  }
556 
557  id = QMetaType::type(instanceName.toLatin1().data());
558  if (!id && !instanceName.startsWith(QStringLiteral("Cutelyst::"))) {
559  instanceName = QLatin1String("Cutelyst::") + instanceName;
560  id = QMetaType::type(instanceName.toLatin1().data());
561  }
562  }
563 
564  if (id) {
565  const QMetaObject *metaObj = QMetaType::metaObjectForType(id);
566  if (metaObj) {
567  if (!superIsClassName(metaObj->superClass(), super)) {
568  qCWarning(CUTELYST_CONTROLLER)
569  << "Class name"
570  << instanceName
571  << "is not a derived class of"
572  << super;
573  }
574 
575  QObject *object = metaObj->newInstance();
576  if (!object) {
577  qCWarning(CUTELYST_CONTROLLER)
578  << "Could create a new instance of"
579  << instanceName
580  << "make sure it's default constructor is "
581  "marked with the Q_INVOKABLE macro";
582  }
583 
584  return object;
585  }
586  } else {
587  Component *component = application->createComponentPlugin(name);
588  if (component) {
589  return component;
590  }
591 
592  component = application->createComponentPlugin(instanceName);
593  if (component) {
594  return component;
595  }
596  }
597 
598  if (!id) {
599  qFatal("Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
600  qPrintable(instanceName), qPrintable(instanceName));
601  }
602  }
603 #endif
604  return nullptr;
605 }
606 
607 bool ControllerPrivate::superIsClassName(const QMetaObject *super, const QByteArray &className)
608 {
609  if (super) {
610  if (super->className() == className) {
611  return true;
612  }
613  return superIsClassName(super->superClass(), className);
614  }
615  return false;
616 }
617 
618 #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
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