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  QVector<Component *> stack;
171  if (d->end) {
172  stack.append(d->end);
173  }
174  stack.append(c->action());
175 
176  auto rit = d->beginAutoList.crbegin();
177  while (rit != d->beginAutoList.crend()) {
178  stack.append(*rit);
179  ++rit;
180  }
181  c->d_ptr->pendingAsync = stack;
182  c->attachAsync();
183 
184 // ActionList stack = d->beginAutoList;
185 // stack.append(c->action());
186 // if (d->end) {
187 // stack.append(d->end);
188 // }
189 // bool &asyncDetached = c->d_ptr->engineRequest->asyncDetached;
190 
191 // // Dispatch to Begin and Auto
192 // const auto beginAutoList = d->beginAutoList;
193 // for (Action *action : beginAutoList) {
194 // if (asyncDetached) {
195 // c->d_ptr->pendingAsync.append(action);
196 // } else if (!action->dispatch(c)) {
197 // ret = false;
198 // break;
199 // }
200 // }
201 
202 // // Dispatch to Action
203 // if (ret) {
204 // if (asyncDetached) {
205 // c->d_ptr->pendingAsync.append(c->action());
206 // } else if (!c->action()->dispatch(c)) {
207 // ret = false;
208 // }
209 // }
210 
211 // // Dispatch to End
212 // if (d->end) {
213 // if (asyncDetached) {
214 // c->d_ptr->pendingAsync.append(d->end);
215 // } else if (!d->end->dispatch(c)) {
216 // ret = false;
217 // }
218 // }
219 
220  return ret;
221 }
222 
223 Action *ControllerPrivate::actionClass(const QVariantHash &args)
224 {
225  const auto attributes = args.value(QStringLiteral("attributes")).value<QMap<QString, QString> >();
226  const QString actionClass = attributes.value(QStringLiteral("ActionClass"));
227 
228  QObject *object = instantiateClass(actionClass, "Cutelyst::Action");
229  if (object) {
230  Action *action = qobject_cast<Action*>(object);
231  if (action) {
232  return action;
233  }
234  qCWarning(CUTELYST_CONTROLLER) << "ActionClass"
235  << actionClass
236  << "is not an ActionClass";
237  delete object;
238  }
239 
240  return new Action;
241 }
242 
243 Action *ControllerPrivate::createAction(const QVariantHash &args, const QMetaMethod &method, Controller *controller, Application *app)
244 {
245  Action *action = actionClass(args);
246  if (!action) {
247  return nullptr;
248  }
249 
250  QStack<Component *> roles = gatherActionRoles(args);
251  for (int i = 0; i < roles.size(); ++i) {
252  Component *code = roles.at(i);
253  code->init(app, args);
254  code->setParent(action);
255  }
256  action->applyRoles(roles);
257  action->setMethod(method);
258  action->setController(controller);
259  action->setName(args.value(QStringLiteral("name")).toString());
260  action->setReverse(args.value(QStringLiteral("reverse")).toString());
261  action->setupAction(args, app);
262 
263  return action;
264 }
265 
266 void ControllerPrivate::registerActionMethods(const QMetaObject *meta, Controller *controller, Application *app)
267 {
268  // Setup actions
269  for (int i = 0; i < meta->methodCount(); ++i) {
270  const QMetaMethod method = meta->method(i);
271  const QByteArray name = method.name();
272 
273  // We register actions that are either a Q_SLOT
274  // or a Q_INVOKABLE function which has the first
275  // parameter type equal to Context*
276  if (method.isValid() &&
277  (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot) &&
278  (method.parameterCount() && method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
279 
280  // Build up the list of attributes for the class info
281  QByteArray attributeArray;
282  for (int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
283  QMetaClassInfo classInfo = meta->classInfo(i2);
284  if (name == classInfo.name()) {
285  attributeArray.append(classInfo.value());
286  }
287  }
288  QMap<QString, QString> attrs = parseAttributes(method, attributeArray, name);
289 
290  QString reverse;
291  if (controller->ns().isEmpty()) {
292  reverse = QString::fromLatin1(name);
293  } else {
294  reverse = controller->ns() + QLatin1Char('/') + QString::fromLatin1(name);
295  }
296 
297  Action *action = createAction({
298  {QStringLiteral("name"), QVariant::fromValue(name)},
299  {QStringLiteral("reverse"), QVariant::fromValue(reverse)},
300  {QStringLiteral("namespace"), QVariant::fromValue(controller->ns())},
301  {QStringLiteral("attributes"), QVariant::fromValue(attrs)}
302  },
303  method,
304  controller,
305  app);
306 
307  actions.insertMulti(action->reverse(), action);
308  actionList.append(action);
309  }
310  }
311 }
312 
313 QMap<QString, QString> ControllerPrivate::parseAttributes(const QMetaMethod &method, const QByteArray &str, const QByteArray &name)
314 {
315  QMap<QString, QString> ret;
316  std::vector<std::pair<QString, QString> > attributes;
317  // This is probably not the best parser ever
318  // but it handles cases like:
319  // :Args:Local('fo"')o'):ActionClass('foo')
320  // into
321  // (("Args",""), ("Local","'fo"')o'"), ("ActionClass","'foo'"))
322 
323  int size = str.size();
324  int pos = 0;
325  while (pos < size) {
326  QString key;
327  QString value;
328 
329  // find the start of a key
330  if (str.at(pos) == ':') {
331  int keyStart = ++pos;
332  int keyLength = 0;
333  while (pos < size) {
334  if (str.at(pos) == '(') {
335  // attribute has value
336  int valueStart = ++pos;
337  while (pos < size) {
338  if (str.at(pos) == ')') {
339  // found the possible end of the value
340  int valueEnd = pos;
341  if (++pos < size && str.at(pos) == ':') {
342  // found the start of a key so this is
343  // really the end of a value
344  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
345  break;
346  } else if (pos >= size) {
347  // found the end of the string
348  // save the remainig as the value
349  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
350  break;
351  }
352  // string was not '):' or ')$'
353  continue;
354  }
355  ++pos;
356  }
357 
358  break;
359  } else if (str.at(pos) == ':') {
360  // Attribute has no value
361  break;
362  }
363  ++keyLength;
364  ++pos;
365  }
366 
367  // stopre the key
368  key = QString::fromLatin1(str.mid(keyStart, keyLength));
369 
370  // remove quotes
371  if (!value.isEmpty()) {
372  if ((value.startsWith(QLatin1Char('\'')) && value.endsWith(QLatin1Char('\''))) ||
373  (value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"')))) {
374  value.remove(0, 1);
375  value.remove(value.size() - 1, 1);
376  }
377  }
378 
379  // store the key/value pair found
380  attributes.push_back({ key, value });
381  continue;
382  }
383  ++pos;
384  }
385 
386  // Add the attributes to the map in the reverse order so
387  // that values() return them in the right order
388  auto i = attributes.crbegin();
389  const auto end = attributes.crend();
390  while (i != end) {
391  QString key = i->first;
392  QString value = i->second;
393  if (key == QLatin1String("Global")) {
394  key = QStringLiteral("Path");
395  value = parsePathAttr(QLatin1Char('/') + QString::fromLatin1(name));
396  } else if (key == QLatin1String("Local")) {
397  key = QStringLiteral("Path");
398  value = parsePathAttr(QString::fromLatin1(name));
399  } else if (key == QLatin1String("Path")) {
400  value = parsePathAttr(value);
401  } else if (key == QLatin1String("Args")) {
402  QString args = value;
403  if (!args.isEmpty()) {
404  value = args.remove(QRegularExpression(QStringLiteral("\\D")));
405  }
406  } else if (key == QLatin1String("CaptureArgs")) {
407  QString captureArgs = value;
408  value = captureArgs.remove(QRegularExpression(QStringLiteral("\\D")));
409  } else if (key == QLatin1String("Chained")) {
410  value = parseChainedAttr(value);
411  }
412 
413  ret.insertMulti(key, value);
414  ++i;
415  }
416 
417  // Handle special AutoArgs and AutoCaptureArgs case
418  if (!ret.contains(QStringLiteral("Args")) && !ret.contains(QStringLiteral("CaptureArgs")) &&
419  (ret.contains(QStringLiteral("AutoArgs")) || ret.contains(QStringLiteral("AutoCaptureArgs")))) {
420  if (ret.contains(QStringLiteral("AutoArgs")) && ret.contains(QStringLiteral("AutoCaptureArgs"))) {
421  qFatal("Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed", name.constData());
422  } else {
423  QString parameterName;
424  if (ret.contains(QStringLiteral("AutoArgs"))) {
425  ret.remove(QStringLiteral("AutoArgs"));
426  parameterName = QStringLiteral("Args");
427  } else {
428  ret.remove(QStringLiteral("AutoCaptureArgs"));
429  parameterName = QStringLiteral("CaptureArgs");
430  }
431 
432  // If the signature is not QStringList we count them
433  if (!(method.parameterCount() == 2 && method.parameterType(1) == QMetaType::QStringList)) {
434  int parameterCount = 0;
435  for (int i2 = 1; i2 < method.parameterCount(); ++i2) {
436  int typeId = method.parameterType(i2);
437  if (typeId == QMetaType::QString) {
438  ++parameterCount;
439  }
440  }
441  ret.insert(parameterName, QString::number(parameterCount));
442  }
443  }
444 
445  }
446 
447  // If the method is private add a Private attribute
448  if (!ret.contains(QStringLiteral("Private")) && method.access() == QMetaMethod::Private) {
449  ret.insert(QStringLiteral("Private"), QString());
450  }
451 
452  return ret;
453 }
454 
455 QStack<Component *> ControllerPrivate::gatherActionRoles(const QVariantHash &args)
456 {
457  QStack<Component *> roles;
458  const auto attributes = args.value(QStringLiteral("attributes")).value<ParamsMultiMap>();
459  auto doesIt = attributes.constFind(QStringLiteral("Does"));
460  while (doesIt != attributes.constEnd() && doesIt.key() == QLatin1String("Does")) {
461  QObject *object = instantiateClass(doesIt.value(), QByteArrayLiteral("Cutelyst::Component"));
462  if (object) {
463  roles.push(qobject_cast<Component *>(object));
464  }
465  ++doesIt;
466  }
467  return roles;
468 }
469 
470 QString ControllerPrivate::parsePathAttr(const QString &value)
471 {
472  QString ret = pathPrefix;
473  if (value.startsWith(QLatin1Char('/'))) {
474  ret = value;
475  } else if (!value.isEmpty()) {
476  ret = pathPrefix + QLatin1Char('/') + value;
477  }
478  return ret;
479 }
480 
481 QString ControllerPrivate::parseChainedAttr(const QString &attr)
482 {
483  QString ret = QStringLiteral("/");
484  if (attr.isEmpty()) {
485  return ret;
486  }
487 
488  if (attr == QStringLiteral(".")) {
489  ret.append(pathPrefix);
490  } else if (!attr.startsWith(QLatin1Char('/'))) {
491  if (!pathPrefix.isEmpty()) {
492  ret.append(pathPrefix + QLatin1Char('/') + attr);
493  } else {
494  // special case namespace '' (root)
495  ret.append(attr);
496  }
497  } else {
498  ret = attr;
499  }
500 
501  return ret;
502 }
503 
504 QObject *ControllerPrivate::instantiateClass(const QString &name, const QByteArray &super)
505 {
506  QString instanceName = name;
507  if (!instanceName.isEmpty()) {
508  instanceName.remove(QRegularExpression(QStringLiteral("\\W")));
509 
510  int id = QMetaType::type(instanceName.toLatin1().data());
511  if (!id) {
512  if (!instanceName.endsWith(QLatin1Char('*'))) {
513  instanceName.append(QLatin1Char('*'));
514  }
515 
516  id = QMetaType::type(instanceName.toLatin1().data());
517  if (!id && !instanceName.startsWith(QStringLiteral("Cutelyst::"))) {
518  instanceName = QLatin1String("Cutelyst::") + instanceName;
519  id = QMetaType::type(instanceName.toLatin1().data());
520  }
521  }
522 
523  if (id) {
524  const QMetaObject *metaObj = QMetaType::metaObjectForType(id);
525  if (metaObj) {
526  if (!superIsClassName(metaObj->superClass(), super)) {
527  qCWarning(CUTELYST_CONTROLLER)
528  << "Class name"
529  << instanceName
530  << "is not a derived class of"
531  << super;
532  }
533 
534  QObject *object = metaObj->newInstance();
535  if (!object) {
536  qCWarning(CUTELYST_CONTROLLER)
537  << "Could create a new instance of"
538  << instanceName
539  << "make sure it's default constructor is "
540  "marked with the Q_INVOKABLE macro";
541  }
542 
543  return object;
544  }
545  } else {
546  Component *component = application->createComponentPlugin(name);
547  if (component) {
548  return component;
549  }
550 
551  component = application->createComponentPlugin(instanceName);
552  if (component) {
553  return component;
554  }
555  }
556 
557  if (!id) {
558  qFatal("Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
559  qPrintable(instanceName), qPrintable(instanceName));
560  }
561  }
562  return nullptr;
563 }
564 
565 bool ControllerPrivate::superIsClassName(const QMetaObject *super, const QByteArray &className)
566 {
567  if (super) {
568  if (super->className() == className) {
569  return true;
570  }
571  return superIsClassName(super->superClass(), className);
572  }
573  return false;
574 }
575 
576 #include "moc_controller.cpp"
Cutelyst::Controller
Cutelyst Controller base class
Definition: controller.h:102
Cutelyst::ParamsMultiMap
QMap< QString, QString > ParamsMultiMap
Definition: paramsmultimap.h:36
Cutelyst::Controller::actions
ActionList actions() const
Definition: controller.cpp:60
Cutelyst::Component::applyRoles
void applyRoles(const QStack< Component * > &roles)
Definition: component.cpp:144
Cutelyst::Action::setupAction
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:57
Cutelyst::Action::setMethod
void setMethod(const QMetaMethod &method)
Definition: action.cpp:38
Cutelyst::Application
The Cutelyst Application.
Definition: application.h:55
Cutelyst::Dispatcher
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
Cutelyst::Context
The Cutelyst Context.
Definition: context.h:50
Cutelyst::Context::attachAsync
void attachAsync()
attachAsync
Definition: context.cpp:363
Cutelyst::Component::init
virtual bool init(Application *application, const QVariantHash &args)
Definition: component.cpp:68
Cutelyst::Component
The Cutelyst Component base class.
Definition: component.h:38
Cutelyst::Controller::preFork
virtual bool preFork(Application *app)
Definition: controller.cpp:71
Cutelyst::Controller::_DISPATCH
bool _DISPATCH(Context *c)
Definition: controller.cpp:164
Cutelyst::Controller::actionFor
Action * actionFor(const QString &name) const
Definition: controller.cpp:50
Cutelyst::Component::setReverse
void setReverse(const QString &reverse)
Definition: component.cpp:62
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::Component::name
QString name() const
Definition: component.cpp:44
Cutelyst::Controller::ns
QString ns() const
Definition: controller.cpp:44
Cutelyst::Action
This class represents a Cutelyst Action.
Definition: action.h:47
Cutelyst::Component::setName
void setName(const QString &name)
Definition: component.cpp:50
Cutelyst::Action::setController
void setController(Controller *controller)
Definition: action.cpp:51
Cutelyst::Controller::postFork
virtual bool postFork(Application *app)
Definition: controller.cpp:77
Cutelyst::Component::reverse
QString reverse() const
Definition: component.cpp:56
Cutelyst::Controller::operator==
bool operator==(const char *className)
Definition: controller.cpp:66
Cutelyst::Controller::Controller
Controller(QObject *parent=nullptr)
Definition: controller.cpp:32
Cutelyst::ActionList
QVector< Action * > ActionList
Definition: action.h:166