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
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
ActionList actions() const
Definition: controller.cpp:60
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
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)
Action * actionFor(const QString &name) const
Definition: controller.cpp:50
Cutelyst Controller base class
Definition: controller.h:102
iterator insertMulti(const Key &key, const T &value)
bool isEmpty() const
const_iterator constEnd() 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:166
void applyRoles(const QStack< Component * > &roles)
Definition: component.cpp:144
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
QString reverse() const
Definition: component.cpp:56
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)
QString ns() const
Definition: controller.cpp:44
int size() const
int size() const
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
const T value(const Key &key, const T &defaultValue) const
int remove(const Key &key)
Controller(QObject *parent=nullptr)
Definition: controller.cpp:32