Cutelyst  2.5.0
controller.cpp
1 /*
2  * Copyright (C) 2013-2018 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 <QMetaClassInfo>
26 #include <QRegularExpression>
27 
28 using namespace Cutelyst;
29 
30 Controller::Controller(QObject *parent) : QObject(parent)
31  , d_ptr(new ControllerPrivate(this))
32 {
33 }
34 
35 Controller::~Controller()
36 {
37  Q_D(Controller);
38  qDeleteAll(d->actionList);
39  delete d_ptr;
40 }
41 
42 QString Controller::ns() const
43 {
44  Q_D(const Controller);
45  return d->pathPrefix;
46 }
47 
48 Action *Controller::actionFor(const QString &name) const
49 {
50  Q_D(const Controller);
51  Action *ret = d->actions.value(name);
52  if (ret) {
53  return ret;
54  }
55  return d->dispatcher->getAction(name, d->pathPrefix);
56 }
57 
59 {
60  Q_D(const Controller);
61  return d->actionList;
62 }
63 
64 bool Controller::operator==(const char *className)
65 {
66  return !qstrcmp(metaObject()->className(), className);
67 }
68 
70 {
71  Q_UNUSED(app)
72  return true;
73 }
74 
76 {
77  Q_UNUSED(app)
78  return true;
79 }
80 
81 ControllerPrivate::ControllerPrivate(Controller *parent) :
82  q_ptr(parent)
83 {
84 }
85 
86 void ControllerPrivate::init(Application *app, Dispatcher *_dispatcher)
87 {
88  Q_Q(Controller);
89 
90  dispatcher = _dispatcher;
91  application = app;
92 
93  // Application must always be our parent
94  q->setParent(app);
95 
96  const QMetaObject *meta = q->metaObject();
97  const QString className = QString::fromLatin1(meta->className());
98  q->setObjectName(className);
99 
100  bool namespaceFound = false;
101  for (int i = meta->classInfoCount() - 1; i >= 0; --i) {
102  if (qstrcmp(meta->classInfo(i).name(), "Namespace") == 0) {
103  pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
104  while (pathPrefix.startsWith(QLatin1Char('/'))) {
105  pathPrefix.remove(0, 1);
106  }
107  namespaceFound = true;
108  break;
109  }
110  }
111 
112  if (!namespaceFound) {
113  QString controlerNS;
114  bool lastWasUpper = true;
115 
116  for (int i = 0; i < className.length(); ++i) {
117  const QChar c = className.at(i);
118  if (c.isLower() || c.isDigit()) {
119  controlerNS.append(c);
120  lastWasUpper = false;
121  } else if (c == QLatin1Char('_')) {
122  controlerNS.append(c);
123  lastWasUpper = true;
124  } else {
125  if (!lastWasUpper) {
126  controlerNS.append(QLatin1Char('/'));
127  }
128  controlerNS.append(c.toLower());
129  lastWasUpper = true;
130  }
131  }
132  pathPrefix = controlerNS;
133  }
134 
135  registerActionMethods(meta, q, app);
136 }
137 
138 void ControllerPrivate::setupFinished()
139 {
140  Q_Q(Controller);
141 
142  const ActionList beginList = dispatcher->getActions(QStringLiteral("Begin"), pathPrefix);
143  if (!beginList.isEmpty()) {
144  beginAutoList.append(beginList.last());
145  }
146 
147  beginAutoList.append(dispatcher->getActions(QStringLiteral("Auto"), pathPrefix));
148 
149  const ActionList endList = dispatcher->getActions(QStringLiteral("End"), pathPrefix);
150  if (!endList.isEmpty()) {
151  end = endList.last();
152  }
153 
154  const auto actions = actionList;
155  for (Action *action : actions) {
156  action->dispatcherReady(dispatcher, q);
157  }
158 
159  q->preFork(qobject_cast<Application *>(q->parent()));
160 }
161 
163 {
164  Q_D(Controller);
165 
166  bool ret = true;
167 
168  // Dispatch to Begin and Auto
169  const auto beginAutoList = d->beginAutoList;
170  for (Action *action : beginAutoList) {
171  if (!action->dispatch(c)) {
172  ret = false;
173  break;
174  }
175  }
176 
177  // Dispatch to Action
178  if (ret && !c->action()->dispatch(c)) {
179  ret = false;
180  }
181 
182  // Dispatch to End
183  if (d->end && !d->end->dispatch(c)) {
184  ret = false;
185  }
186 
187  return ret;
188 }
189 
190 Action *ControllerPrivate::actionClass(const QVariantHash &args)
191 {
192  const auto attributes = args.value(QStringLiteral("attributes")).value<QMap<QString, QString> >();
193  const QString actionClass = attributes.value(QStringLiteral("ActionClass"));
194 
195  QObject *object = instantiateClass(actionClass, "Cutelyst::Action");
196  if (object) {
197  Action *action = qobject_cast<Action*>(object);
198  if (action) {
199  return action;
200  }
201  qCWarning(CUTELYST_CONTROLLER) << "ActionClass"
202  << actionClass
203  << "is not an ActionClass";
204  delete object;
205  }
206 
207  return new Action;
208 }
209 
210 Action *ControllerPrivate::createAction(const QVariantHash &args, const QMetaMethod &method, Controller *controller, Application *app)
211 {
212  Action *action = actionClass(args);
213  if (!action) {
214  return nullptr;
215  }
216 
217  QStack<Component *> roles = gatherActionRoles(args);
218  for (int i = 0; i < roles.size(); ++i) {
219  Component *code = roles.at(i);
220  code->init(app, args);
221  code->setParent(action);
222  }
223  action->applyRoles(roles);
224  action->setMethod(method);
225  action->setController(controller);
226  action->setName(args.value(QStringLiteral("name")).toString());
227  action->setReverse(args.value(QStringLiteral("reverse")).toString());
228  action->setupAction(args, app);
229 
230  return action;
231 }
232 
233 void ControllerPrivate::registerActionMethods(const QMetaObject *meta, Controller *controller, Application *app)
234 {
235  // Setup actions
236  for (int i = 0; i < meta->methodCount(); ++i) {
237  const QMetaMethod method = meta->method(i);
238  const QByteArray name = method.name();
239 
240  // We register actions that are either a Q_SLOT
241  // or a Q_INVOKABLE function which has the first
242  // parameter type equal to Context*
243  if (method.isValid() &&
244  (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot) &&
245  (method.parameterCount() && method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
246 
247  // Build up the list of attributes for the class info
248  QByteArray attributeArray;
249  for (int i = meta->classInfoCount() - 1; i >= 0; --i) {
250  QMetaClassInfo classInfo = meta->classInfo(i);
251  if (name == classInfo.name()) {
252  attributeArray.append(classInfo.value());
253  }
254  }
255  QMap<QString, QString> attrs = parseAttributes(method, attributeArray, name);
256 
257  QString reverse;
258  if (controller->ns().isEmpty()) {
259  reverse = QString::fromLatin1(name);
260  } else {
261  reverse = controller->ns() + QLatin1Char('/') + QString::fromLatin1(name);
262  }
263 
264  Action *action = createAction({
265  {QStringLiteral("name"), QVariant::fromValue(name)},
266  {QStringLiteral("reverse"), QVariant::fromValue(reverse)},
267  {QStringLiteral("namespace"), QVariant::fromValue(controller->ns())},
268  {QStringLiteral("attributes"), QVariant::fromValue(attrs)}
269  },
270  method,
271  controller,
272  app);
273 
274  actions.insertMulti(action->reverse(), action);
275  actionList.append(action);
276  }
277  }
278 }
279 
280 QMap<QString, QString> ControllerPrivate::parseAttributes(const QMetaMethod &method, const QByteArray &str, const QByteArray &name)
281 {
282  QMap<QString, QString> ret;
283  std::vector<std::pair<QString, QString> > attributes;
284  // This is probably not the best parser ever
285  // but it handles cases like:
286  // :Args:Local('fo"')o'):ActionClass('foo')
287  // into
288  // (("Args",""), ("Local","'fo"')o'"), ("ActionClass","'foo'"))
289 
290  int size = str.size();
291  int pos = 0;
292  while (pos < size) {
293  QString key;
294  QString value;
295 
296  // find the start of a key
297  if (str.at(pos) == ':') {
298  int keyStart = ++pos;
299  int keyLength = 0;
300  while (pos < size) {
301  if (str.at(pos) == '(') {
302  // attribute has value
303  int valueStart = ++pos;
304  while (pos < size) {
305  if (str.at(pos) == ')') {
306  // found the possible end of the value
307  int valueEnd = pos;
308  if (++pos < size && str.at(pos) == ':') {
309  // found the start of a key so this is
310  // really the end of a value
311  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
312  break;
313  } else if (pos >= size) {
314  // found the end of the string
315  // save the remainig as the value
316  value = QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
317  break;
318  }
319  // string was not '):' or ')$'
320  continue;
321  }
322  ++pos;
323  }
324 
325  break;
326  } else if (str.at(pos) == ':') {
327  // Attribute has no value
328  break;
329  }
330  ++keyLength;
331  ++pos;
332  }
333 
334  // stopre the key
335  key = QString::fromLatin1(str.mid(keyStart, keyLength));
336 
337  // remove quotes
338  if (!value.isEmpty()) {
339  if ((value.startsWith(QLatin1Char('\'')) && value.endsWith(QLatin1Char('\''))) ||
340  (value.startsWith(QLatin1Char('"')) && value.endsWith(QLatin1Char('"')))) {
341  value.remove(0, 1);
342  value.remove(value.size() - 1, 1);
343  }
344  }
345 
346  // store the key/value pair found
347  attributes.push_back({ key, value });
348  continue;
349  }
350  ++pos;
351  }
352 
353  // Add the attributes to the map in the reverse order so
354  // that values() return them in the right order
355  auto i = attributes.crbegin();
356  const auto end = attributes.crend();
357  while (i != end) {
358  QString key = i->first;
359  QString value = i->second;
360  if (key == QLatin1String("Global")) {
361  key = QStringLiteral("Path");
362  value = parsePathAttr(QLatin1Char('/') + QString::fromLatin1(name));
363  } else if (key == QLatin1String("Local")) {
364  key = QStringLiteral("Path");
365  value = parsePathAttr(QString::fromLatin1(name));
366  } else if (key == QLatin1String("Path")) {
367  value = parsePathAttr(value);
368  } else if (key == QLatin1String("Args")) {
369  QString args = value;
370  if (!args.isEmpty()) {
371  value = args.remove(QRegularExpression(QStringLiteral("\\D")));
372  }
373  } else if (key == QLatin1String("CaptureArgs")) {
374  QString captureArgs = value;
375  value = captureArgs.remove(QRegularExpression(QStringLiteral("\\D")));
376  } else if (key == QLatin1String("Chained")) {
377  value = parseChainedAttr(value);
378  }
379 
380  ret.insertMulti(key, value);
381  ++i;
382  }
383 
384  // Handle special AutoArgs and AutoCaptureArgs case
385  if (!ret.contains(QStringLiteral("Args")) && !ret.contains(QStringLiteral("CaptureArgs")) &&
386  (ret.contains(QStringLiteral("AutoArgs")) || ret.contains(QStringLiteral("AutoCaptureArgs")))) {
387  if (ret.contains(QStringLiteral("AutoArgs")) && ret.contains(QStringLiteral("AutoCaptureArgs"))) {
388  qFatal("Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed", name.constData());
389  } else {
390  QString parameterName;
391  if (ret.contains(QStringLiteral("AutoArgs"))) {
392  ret.remove(QStringLiteral("AutoArgs"));
393  parameterName = QStringLiteral("Args");
394  } else {
395  ret.remove(QStringLiteral("AutoCaptureArgs"));
396  parameterName = QStringLiteral("CaptureArgs");
397  }
398 
399  // If the signature is not QStringList we count them
400  if (!(method.parameterCount() == 2 && method.parameterType(1) == QMetaType::QStringList)) {
401  int parameterCount = 0;
402  for (int i = 1; i < method.parameterCount(); ++i) {
403  int typeId = method.parameterType(i);
404  if (typeId == QMetaType::QString) {
405  ++parameterCount;
406  }
407  }
408  ret.insert(parameterName, QString::number(parameterCount));
409  }
410  }
411 
412  }
413 
414  // If the method is private add a Private attribute
415  if (!ret.contains(QStringLiteral("Private")) && method.access() == QMetaMethod::Private) {
416  ret.insert(QStringLiteral("Private"), QString());
417  }
418 
419  return ret;
420 }
421 
422 QStack<Component *> ControllerPrivate::gatherActionRoles(const QVariantHash &args)
423 {
424  QStack<Component *> roles;
425  const auto attributes = args.value(QStringLiteral("attributes")).value<ParamsMultiMap>();
426  auto doesIt = attributes.constFind(QStringLiteral("Does"));
427  while (doesIt != attributes.constEnd() && doesIt.key() == QLatin1String("Does")) {
428  QObject *object = instantiateClass(doesIt.value(), QByteArrayLiteral("Cutelyst::Component"));
429  if (object) {
430  roles.push(qobject_cast<Component *>(object));
431  }
432  ++doesIt;
433  }
434  return roles;
435 }
436 
437 QString ControllerPrivate::parsePathAttr(const QString &value)
438 {
439  QString ret = pathPrefix;
440  if (value.startsWith(QLatin1Char('/'))) {
441  ret = value;
442  } else if (!value.isEmpty()) {
443  ret = pathPrefix + QLatin1Char('/') + value;
444  }
445  return ret;
446 }
447 
448 QString ControllerPrivate::parseChainedAttr(const QString &attr)
449 {
450  QString ret = QStringLiteral("/");
451  if (attr.isEmpty()) {
452  return ret;
453  }
454 
455  if (attr == QStringLiteral(".")) {
456  ret.append(pathPrefix);
457  } else if (!attr.startsWith(QLatin1Char('/'))) {
458  if (!pathPrefix.isEmpty()) {
459  ret.append(pathPrefix + QLatin1Char('/') + attr);
460  } else {
461  // special case namespace '' (root)
462  ret.append(attr);
463  }
464  } else {
465  ret = attr;
466  }
467 
468  return ret;
469 }
470 
471 QObject *ControllerPrivate::instantiateClass(const QString &name, const QByteArray &super)
472 {
473  QString instanceName = name;
474  if (!instanceName.isEmpty()) {
475  instanceName.remove(QRegularExpression(QStringLiteral("\\W")));
476 
477  int id = QMetaType::type(instanceName.toLatin1().data());
478  if (!id) {
479  if (!instanceName.endsWith(QLatin1Char('*'))) {
480  instanceName.append(QLatin1Char('*'));
481  }
482 
483  id = QMetaType::type(instanceName.toLatin1().data());
484  if (!id && !instanceName.startsWith(QStringLiteral("Cutelyst::"))) {
485  instanceName = QLatin1String("Cutelyst::") + instanceName;
486  id = QMetaType::type(instanceName.toLatin1().data());
487  }
488  }
489 
490  if (id) {
491  const QMetaObject *metaObj = QMetaType::metaObjectForType(id);
492  if (metaObj) {
493  if (!superIsClassName(metaObj->superClass(), super)) {
494  qCWarning(CUTELYST_CONTROLLER)
495  << "Class name"
496  << instanceName
497  << "is not a derived class of"
498  << super;
499  }
500 
501  QObject *object = metaObj->newInstance();
502  if (!object) {
503  qCWarning(CUTELYST_CONTROLLER)
504  << "Could create a new instance of"
505  << instanceName
506  << "make sure it's default constructor is "
507  "marked with the Q_INVOKABLE macro";
508  }
509 
510  return object;
511  }
512  } else {
513  Component *component = application->createComponentPlugin(name);
514  if (component) {
515  return component;
516  }
517 
518  component = application->createComponentPlugin(instanceName);
519  if (component) {
520  return component;
521  }
522  }
523 
524  if (!id) {
525  qFatal("Could not create component '%s', you can register it with qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
526  qPrintable(instanceName), qPrintable(instanceName));
527  }
528  }
529  return nullptr;
530 }
531 
532 bool ControllerPrivate::superIsClassName(const QMetaObject *super, const QByteArray &className)
533 {
534  if (super) {
535  if (super->className() == className) {
536  return true;
537  }
538  return superIsClassName(super->superClass(), className);
539  }
540  return false;
541 }
542 
543 #include "moc_controller.cpp"
QMap< QString, QString > ParamsMultiMap
void setName(const QString &name)
Definition: component.cpp:45
QVector< Action * > ActionList
Definition: action.h:162
bool dispatch(Context *c)
Definition: action.h:94
void setReverse(const QString &reverse)
Definition: component.cpp:57
virtual bool preFork(Application *app)
Definition: controller.cpp:69
Action * actionFor(const QString &name) const
Definition: controller.cpp:48
The Cutelyst Component base class.
Definition: component.h:38
This class represents a Cutelyst Action.
Definition: action.h:47
The Cutelyst Context.
Definition: context.h:50
Cutelyst Controller base class
Definition: controller.h:102
QString name() const
Definition: component.cpp:39
virtual bool init(Application *application, const QVariantHash &args)
Definition: component.cpp:63
bool _DISPATCH(Context *c)
Definition: controller.cpp:162
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void applyRoles(const QStack< Component *> &roles)
Definition: component.cpp:139
QString reverse() const
Definition: component.cpp:51
void setController(Controller *controller)
Definition: action.cpp:53
virtual bool postFork(Application *app)
Definition: controller.cpp:75
void setupAction(const QVariantHash &args, Application *app)
Definition: action.cpp:59
bool operator==(const char *className)
Definition: controller.cpp:64
The Cutelyst Application.
Definition: application.h:55
void setMethod(const QMetaMethod &method)
Definition: action.cpp:40
ActionList actions() const
Definition: controller.cpp:58
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
QString ns() const
Definition: controller.cpp:42
Controller(QObject *parent=nullptr)
Definition: controller.cpp:30