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 
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  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
QString & append(QChar ch)
bool dispatch(Context *c)
Definition: action.h:94
bool contains(const Key &key) const
QByteArray name() const
void append(const T &value)
char at(int i) const
const QMetaObject * metaObjectForType(int type)
void setReverse(const QString &reverse)
Definition: component.cpp:62
const QMetaObject * superClass() const
void push(const T &t)
bool isDigit() const
T & last()
int size() const
virtual const QMetaObject * metaObject() const
virtual bool preFork(Application *app)
Definition: controller.cpp:71
const_iterator constFind(const Key &key) const
Action * actionFor(const QString &name) const
Definition: controller.cpp:50
const char * name() const
QString & remove(int position, int n)
int type(const char *typeName)
Access access() const
The Cutelyst Component base class.
Definition: component.h:38
T value(int i) const
This class represents a Cutelyst Action.
Definition: action.h:47
The Cutelyst Context.
Definition: context.h:51
QString number(int n, int base)
Cutelyst Controller base class
Definition: controller.h:102
iterator insertMulti(const Key &key, const T &value)
bool isEmpty() const
int methodCount() const
const char * constData() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
bool isValid() const
int classInfoCount() const
int parameterCount() const
const char * value() const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
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
QByteArray mid(int pos, int len) const
QMetaClassInfo classInfo(int index) const
int parameterType(int index) const
QByteArray & append(char ch)
void push_back(QChar ch)
void setParent(QObject *parent)
const char * className() const
QVariant fromValue(const T &value)
QChar toLower() const
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
MethodType methodType() const
const T & at(int i) const
QByteArray toLatin1() const
bool isEmpty() const
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:57
const QChar at(int position) const
int length() const
char * data()
QString fromLatin1(const char *str, int size)
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
iterator insert(const Key &key, const T &value)
int size() const
int size() const
ActionList actions() const
Definition: controller.cpp:60
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
QObject * newInstance(QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) const
QMetaMethod method(int index) const
bool isLower() const
QString ns() const
Definition: controller.cpp:44
const T value(const Key &key, const T &defaultValue) const
int remove(const Key &key)
Controller(QObject *parent=nullptr)
Definition: controller.cpp:32