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