Cutelyst  2.12.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  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 {
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"
void setName(const QString &name)
Definition: component.cpp:50
QString & append(QChar ch)
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:50
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
void attachAsync()
attachAsync
Definition: context.cpp:363
const T value(const Key &key, const T &defaultValue) const
int remove(const Key &key)
Controller(QObject *parent=nullptr)
Definition: controller.cpp:32