Cutelyst  2.13.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 
32 Controller::Controller(QObject *parent) : QObject(parent)
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 
44 QString Controller::ns() const
45 {
46  Q_D(const Controller);
47  return d->pathPrefix;
48 }
49 
50 Action *Controller::actionFor(const QString &name) const
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  controlerNS.append(c.toLower());
131  lastWasUpper = true;
132  }
133  }
134  pathPrefix = controlerNS;
135  }
136 
137  registerActionMethods(meta, q, app);
138 }
139 
140 void ControllerPrivate::setupFinished()
141 {
142  Q_Q(Controller);
143 
144  const ActionList beginList = dispatcher->getActions(QStringLiteral("Begin"), pathPrefix);
145  if (!beginList.isEmpty()) {
146  beginAutoList.append(beginList.last());
147  }
148 
149  beginAutoList.append(dispatcher->getActions(QStringLiteral("Auto"), pathPrefix));
150 
151  const ActionList endList = dispatcher->getActions(QStringLiteral("End"), pathPrefix);
152  if (!endList.isEmpty()) {
153  end = endList.last();
154  }
155 
156  const auto actions = actionList;
157  for (Action *action : actions) {
158  action->dispatcherReady(dispatcher, q);
159  }
160 
161  q->preFork(qobject_cast<Application *>(q->parent()));
162 }
163 
165 {
166  Q_D(Controller);
167 
168  bool ret = true;
169 
170  int &asyncDetached = c->d_ptr->asyncDetached;
171 
172  // Dispatch to Begin and Auto
173  const auto beginAutoList = d->beginAutoList;
174  for (Action *action : beginAutoList) {
175  if (asyncDetached) {
176  c->d_ptr->pendingAsync.append(action);
177  } else if (!action->dispatch(c)) {
178  ret = false;
179  break;
180  }
181  }
182 
183  // Dispatch to Action
184  if (ret) {
185  if (asyncDetached) {
186  c->d_ptr->pendingAsync.append(c->action());
187  } else if (!c->action()->dispatch(c)) {
188  ret = false;
189  }
190  }
191 
192  // Dispatch to End
193  if (d->end) {
194  if (asyncDetached) {
195  c->d_ptr->pendingAsync.append(d->end);
196  } else if (!d->end->dispatch(c)) {
197  ret = false;
198  }
199  }
200 
201  return ret;
202 }
203 
204 Action *ControllerPrivate::actionClass(const QVariantHash &args)
205 {
206  const auto attributes = args.value(QStringLiteral("attributes")).value<QMap<QString, QString> >();
207  const QString actionClass = attributes.value(QStringLiteral("ActionClass"));
208 
209  QObject *object = instantiateClass(actionClass, "Cutelyst::Action");
210  if (object) {
211  Action *action = qobject_cast<Action*>(object);
212  if (action) {
213  return action;
214  }
215  qCWarning(CUTELYST_CONTROLLER) << "ActionClass"
216  << actionClass
217  << "is not an ActionClass";
218  delete object;
219  }
220 
221  return new Action;
222 }
223 
224 Action *ControllerPrivate::createAction(const QVariantHash &args, const QMetaMethod &method, Controller *controller, Application *app)
225 {
226  Action *action = actionClass(args);
227  if (!action) {
228  return nullptr;
229  }
230 
231  QStack<Component *> roles = gatherActionRoles(args);
232  for (int i = 0; i < roles.size(); ++i) {
233  Component *code = roles.at(i);
234  code->init(app, args);
235  code->setParent(action);
236  }
237  action->applyRoles(roles);
238  action->setMethod(method);
239  action->setController(controller);
240  action->setName(args.value(QStringLiteral("name")).toString());
241  action->setReverse(args.value(QStringLiteral("reverse")).toString());
242  action->setupAction(args, app);
243 
244  return action;
245 }
246 
247 void ControllerPrivate::registerActionMethods(const QMetaObject *meta, Controller *controller, Application *app)
248 {
249  // Setup actions
250  for (int i = 0; i < meta->methodCount(); ++i) {
251  const QMetaMethod method = meta->method(i);
252  const QByteArray name = method.name();
253 
254  // We register actions that are either a Q_SLOT
255  // or a Q_INVOKABLE function which has the first
256  // parameter type equal to Context*
257  if (method.isValid() &&
258  (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot) &&
259  (method.parameterCount() && method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
260 
261  // Build up the list of attributes for the class info
262  QByteArray attributeArray;
263  for (int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
264  QMetaClassInfo classInfo = meta->classInfo(i2);
265  if (name == classInfo.name()) {
266  attributeArray.append(classInfo.value());
267  }
268  }
269  QMap<QString, QString> attrs = parseAttributes(method, attributeArray, name);
270 
271  QString reverse;
272  if (controller->ns().isEmpty()) {
273  reverse = QString::fromLatin1(name);
274  } else {
275  reverse = controller->ns() + QLatin1Char('/') + QString::fromLatin1(name);
276  }
277 
278  Action *action = createAction({
279  {QStringLiteral("name"), QVariant::fromValue(name)},
280  {QStringLiteral("reverse"), QVariant::fromValue(reverse)},
281  {QStringLiteral("namespace"), QVariant::fromValue(controller->ns())},
282  {QStringLiteral("attributes"), QVariant::fromValue(attrs)}
283  },
284  method,
285  controller,
286  app);
287 
288  actions.insertMulti(action->reverse(), action);
289  actionList.append(action);
290  }
291  }
292 }
293 
294 QMap<QString, QString> ControllerPrivate::parseAttributes(const QMetaMethod &method, const QByteArray &str, const QByteArray &name)
295 {
297  std::vector<std::pair<QString, QString> > attributes;
298  // This is probably not the best parser ever
299  // but it handles cases like:
300  // :Args:Local('fo"')o'):ActionClass('foo')
301  // into
302  // (("Args",""), ("Local","'fo"')o'"), ("ActionClass","'foo'"))
303 
304  int size = str.size();
305  int pos = 0;
306  while (pos < size) {
307  QString key;
308  QString value;
309 
310  // find the start of a key
311  if (str.at(pos) == ':') {
312  int keyStart = ++pos;
313  int keyLength = 0;
314  while (pos < size) {
315  if (str.at(pos) == '(') {
316  // attribute has value
317  int valueStart = ++pos;
318  while (pos < size) {
319  if (str.at(pos) == ')') {
320  // found the possible end of the value
321  int valueEnd = pos;
322  if (++pos < size && str.at(pos) == ':') {
323  // found the start of a key so this is
324  // really the end of a value
325  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
326  break;
327  } else if (pos >= size) {
328  // found the end of the string
329  // save the remainig as the value
330  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
331  break;
332  }
333  // string was not '):' or ')$'
334  continue;
335  }
336  ++pos;
337  }
338 
339  break;
340  } else if (str.at(pos) == ':') {
341  // Attribute has no value
342  break;
343  }
344  ++keyLength;
345  ++pos;
346  }
347 
348  // stopre the key
349  key = QString::fromLatin1(str.mid(keyStart, keyLength));
350 
351  // remove quotes
352  if (!value.isEmpty()) {
353  if ((value.startsWith(QLatin1Char('\'')) && value.endsWith(QLatin1Char('\''))) ||
354  (value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"')))) {
355  value.remove(0, 1);
356  value.remove(value.size() - 1, 1);
357  }
358  }
359 
360  // store the key/value pair found
361  attributes.push_back({ key, value });
362  continue;
363  }
364  ++pos;
365  }
366 
367  // Add the attributes to the map in the reverse order so
368  // that values() return them in the right order
369  auto i = attributes.crbegin();
370  const auto end = attributes.crend();
371  while (i != end) {
372  QString key = i->first;
373  QString value = i->second;
374  if (key == QLatin1String("Global")) {
375  key = QStringLiteral("Path");
376  value = parsePathAttr(QLatin1Char('/') + QString::fromLatin1(name));
377  } else if (key == QLatin1String("Local")) {
378  key = QStringLiteral("Path");
379  value = parsePathAttr(QString::fromLatin1(name));
380  } else if (key == QLatin1String("Path")) {
381  value = parsePathAttr(value);
382  } else if (key == QLatin1String("Args")) {
383  QString args = value;
384  if (!args.isEmpty()) {
385  value = args.remove(QRegularExpression(QStringLiteral("\\D")));
386  }
387  } else if (key == QLatin1String("CaptureArgs")) {
388  QString captureArgs = value;
389  value = captureArgs.remove(QRegularExpression(QStringLiteral("\\D")));
390  } else if (key == QLatin1String("Chained")) {
391  value = parseChainedAttr(value);
392  }
393 
394  ret.insertMulti(key, value);
395  ++i;
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.insert(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.insert(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() == QLatin1String("Does")) {
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 == QStringLiteral(".")) {
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  int id = QMetaType::type(instanceName.toLatin1().data());
492  if (!id) {
493  if (!instanceName.endsWith(QLatin1Char('*'))) {
494  instanceName.append(QLatin1Char('*'));
495  }
496 
497  id = QMetaType::type(instanceName.toLatin1().data());
498  if (!id && !instanceName.startsWith(QStringLiteral("Cutelyst::"))) {
499  instanceName = QLatin1String("Cutelyst::") + instanceName;
500  id = QMetaType::type(instanceName.toLatin1().data());
501  }
502  }
503 
504  if (id) {
505  const QMetaObject *metaObj = QMetaType::metaObjectForType(id);
506  if (metaObj) {
507  if (!superIsClassName(metaObj->superClass(), super)) {
508  qCWarning(CUTELYST_CONTROLLER)
509  << "Class name"
510  << instanceName
511  << "is not a derived class of"
512  << super;
513  }
514 
515  QObject *object = metaObj->newInstance();
516  if (!object) {
517  qCWarning(CUTELYST_CONTROLLER)
518  << "Could create a new instance of"
519  << instanceName
520  << "make sure it's default constructor is "
521  "marked with the Q_INVOKABLE macro";
522  }
523 
524  return object;
525  }
526  } else {
527  Component *component = application->createComponentPlugin(name);
528  if (component) {
529  return component;
530  }
531 
532  component = application->createComponentPlugin(instanceName);
533  if (component) {
534  return component;
535  }
536  }
537 
538  if (!id) {
539  qFatal("Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
540  qPrintable(instanceName), qPrintable(instanceName));
541  }
542  }
543  return nullptr;
544 }
545 
546 bool ControllerPrivate::superIsClassName(const QMetaObject *super, const QByteArray &className)
547 {
548  if (super) {
549  if (super->className() == className) {
550  return true;
551  }
552  return superIsClassName(super->superClass(), className);
553  }
554  return false;
555 }
556 
557 #include "moc_controller.cpp"
void setName(const QString &name)
Definition: component.cpp:50
QVector< Action * > ActionList
Definition: action.h:166
bool dispatch(Context *c)
Definition: action.h:94
bool contains(const Key &key) const const
void setReverse(const QString &reverse)
Definition: component.cpp:62
virtual bool preFork(Application *app)
Definition: controller.cpp:71
QMap::const_iterator constFind(const Key &key) const const
Action * actionFor(const QString &name) const
Definition: controller.cpp:50
The Cutelyst Component base class.
Definition: component.h:38
This class represents a Cutelyst Action.
Definition: action.h:47
The Cutelyst Context.
Definition: context.h:51
Cutelyst Controller base class
Definition: controller.h:102
QMap::iterator insertMulti(const Key &key, const T &value)
QString name() const
Definition: component.cpp:44
virtual bool init(Application *application, const QVariantHash &args)
Definition: component.cpp:68
bool _DISPATCH(Context *c)
Definition: controller.cpp:164
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void applyRoles(const QStack< Component * > &roles)
Definition: component.cpp:144
QString reverse() const
Definition: component.cpp:56
void setController(Controller *controller)
Definition: action.cpp:51
virtual bool postFork(Application *app)
Definition: controller.cpp:77
const Key key(const T &value, const Key &defaultKey) const const
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:57
bool operator==(const char *className)
Definition: controller.cpp:66
The Cutelyst Application.
Definition: application.h:55
void setMethod(const QMetaMethod &method)
Definition: action.cpp:38
QMap::iterator insert(const Key &key, const T &value)
ActionList actions() const
Definition: controller.cpp:60
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
int size() const const
QString ns() const
Definition: controller.cpp:44
const T value(const Key &key, const T &defaultValue) const const
int remove(const Key &key)
Controller(QObject *parent=nullptr)
Definition: controller.cpp:32