Cutelyst  3.5.0
dispatcher.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "dispatcher_p.h"
6 
7 #include "common.h"
8 #include "application.h"
9 #include "engine.h"
10 #include "context.h"
11 #include "controller.h"
12 #include "controller_p.h"
13 #include "action.h"
14 #include "request_p.h"
15 #include "dispatchtypepath.h"
16 #include "dispatchtypechained.h"
17 #include "utils.h"
18 
19 #include <QUrl>
20 #include <QMetaMethod>
21 
22 using namespace Cutelyst;
23 
25  , d_ptr(new DispatcherPrivate(this))
26 {
29 }
30 
31 Dispatcher::~Dispatcher()
32 {
33  delete d_ptr;
34 }
35 
36 void Dispatcher::setupActions(const QVector<Controller*> &controllers, const QVector<Cutelyst::DispatchType *> &dispatchers, bool printActions)
37 {
38  Q_D(Dispatcher);
39 
40  d->dispatchers = dispatchers;
41 
42  ActionList registeredActions;
43  for (Controller *controller : controllers) {
44  bool instanceUsed = false;
45  const auto actions = controller->actions();
46  for (Action *action : actions) {
47  bool registered = false;
48  if (!d->actions.contains(action->reverse())) {
49  if (!action->attributes().contains(QStringLiteral("Private"))) {
50  // Register the action with each dispatcher
52  if (dispatch->registerAction(action)) {
53  registered = true;
54  }
55  }
56  } else {
57  // We register private actions
58  registered = true;
59  }
60  }
61 
62  // The Begin, Auto, End actions are not
63  // registered by Dispatchers but we need them
64  // as private actions anyway
65  if (registered) {
66  const QString name = action->ns() + QLatin1Char('/') + action->name();
67  d->actions.insert(name, { name, action });
68  d->actionContainer[action->ns()] << action;
69  registeredActions.append(action);
70  instanceUsed = true;
71  } else {
72  qCDebug(CUTELYST_DISPATCHER) << "The action" << action->name() << "of"
73  << action->controller()->objectName()
74  << "controller was not registered in any dispatcher."
75  " If you still want to access it internally (via actionFor())"
76  " you may make it's method private.";
77  }
78  }
79 
80  if (instanceUsed) {
81  d->controllers.insert(controller->objectName(), controller);
82  }
83  }
84 
85  if (printActions) {
86  d->printActions();
87  }
88 
89  // Cache root actions, BEFORE the controllers set them
90  d->rootActions = d->actionContainer.value(QLatin1String(""));
91 
92  for (Controller *controller : controllers) {
93  controller->d_ptr->setupFinished();
94  }
95 
96  // Unregister any dispatcher that is not in use
97  int i = 0;
98  while (i < d->dispatchers.size()) {
99  DispatchType *type = d->dispatchers.at(i);
100  if (!type->inUse()) {
101  d->dispatchers.removeAt(i);
102  continue;
103  }
104  ++i;
105  }
106 
107  if (printActions) {
108  // List all public actions
110  qCDebug(CUTELYST_DISPATCHER) << dispatch->list().constData();
111  }
112  }
113 }
114 
116 {
117  Action *action = c->action();
118  if (action) {
119  return action->controller()->_DISPATCH(c);
120  } else {
121  const QString path = c->req()->path();
122  if (path.isEmpty()) {
123  c->error(c->translate("Cutelyst::Dispatcher", "No default action defined"));
124  } else {
125  c->error(c->translate("Cutelyst::Dispatcher", "Unknown resource '%1'.").arg(path));
126  }
127  }
128  return false;
129 }
130 
132 {
133  Q_ASSERT(component);
134  // If the component was an Action
135  // the dispatch() would call c->execute
136  return c->execute(component);
137 }
138 
139 bool Dispatcher::forward(Context *c, const QString &opname)
140 {
141  Q_D(const Dispatcher);
142 
143  Action *action = d->command2Action(c, opname, c->request()->args());
144  if (action) {
145  return action->dispatch(c);
146  }
147 
148  qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
149  return false;
150 }
151 
153 {
154  Q_D(Dispatcher);
155 
156  Request *request = c->request();
157  d->prepareAction(c, request->path());
158 
159  static const auto &log = CUTELYST_DISPATCHER();
160  if (log.isDebugEnabled()) {
161  if (!request->match().isEmpty()) {
162  qCDebug(log) << "Path is" << request->match();
163  }
164 
165  if (!request->args().isEmpty()) {
166  qCDebug(log) << "Arguments are" << request->args().join(QLatin1Char('/'));
167  }
168  }
169 }
170 
171 void DispatcherPrivate::prepareAction(Context *c, const QString &requestPath) const
172 {
173  QString path = normalizePath(requestPath);
174  QStringList args;
175 
176  // "foo/bar"
177  // "foo/" skip
178  // "foo"
179  // ""
180  Q_FOREVER {
181  // Check out the dispatch types to see if any
182  // will handle the path at this level
183  for (DispatchType *type : dispatchers) {
184  if (type->match(c, path, args) == DispatchType::ExactMatch) {
185  return;
186  }
187  }
188 
189  // leave the loop if we are at the root "/"
190  if (path.isEmpty()) {
191  break;
192  }
193 
194  int pos = path.lastIndexOf(QLatin1Char('/'));
195 
196  const QString arg = path.mid(pos + 1);
197  args.prepend(arg);
198 
199  path.resize(pos);
200  }
201 }
202 
203 Action *Dispatcher::getAction(const QString &name, const QString &nameSpace) const
204 {
205  Q_D(const Dispatcher);
206 
207  if (name.isEmpty()) {
208  return nullptr;
209  }
210 
211  if (nameSpace.isEmpty()) {
212  const QString normName = QLatin1Char('/') + name;
213  return d->actions.value(normName).action;
214  }
215 
216  const QString ns = DispatcherPrivate::cleanNamespace(nameSpace);
217  return getActionByPath(ns + QLatin1Char('/') + name);
218 }
219 
221 {
222  Q_D(const Dispatcher);
223 
224  QString _path = path;
225  int slashes = _path.count(QLatin1Char('/'));
226  if (slashes == 0) {
227  _path.prepend(QLatin1Char('/'));
228  } else if (_path.startsWith(QLatin1Char('/')) && slashes != 1) {
229  _path.remove(0, 1);
230  }
231  return d->actions.value(_path).action;
232 }
233 
234 ActionList Dispatcher::getActions(const QString &name, const QString &nameSpace) const
235 {
236  Q_D(const Dispatcher);
237 
238  ActionList ret;
239 
240  if (name.isEmpty()) {
241  return ret;
242  }
243 
244  const QString ns = DispatcherPrivate::cleanNamespace(nameSpace);
245  const ActionList containers = d->getContainers(ns);
246  auto rIt = containers.rbegin();
247  while (rIt != containers.rend()) {
248  if ((*rIt)->name() == name) {
249  ret.append(*rIt);
250  }
251  ++rIt;
252  }
253  return ret;
254 }
255 
257 {
258  Q_D(const Dispatcher);
259  return d->controllers;
260 }
261 
262 QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
263 {
264  Q_D(const Dispatcher);
265  QString ret;
266  for (DispatchType *dispatch : d->dispatchers) {
267  ret = dispatch->uriForAction(action, captures);
268  if (!ret.isNull()) {
269  if (ret.isEmpty()) {
270  ret = QStringLiteral("/");
271  }
272  break;
273  }
274  }
275  return ret;
276 }
277 
279 {
280  Q_D(const Dispatcher);
281  for (DispatchType *dispatch : d->dispatchers) {
282  Action *expandedAction = dispatch->expandAction(c, action);
283  if (expandedAction) {
284  return expandedAction;
285  }
286  }
287  return action;
288 }
289 
291 {
292  Q_D(const Dispatcher);
293  return d->dispatchers;
294 }
295 
296 QString DispatcherPrivate::cleanNamespace(const QString &ns)
297 {
298  QString ret = ns;
299  bool lastWasSlash = true; // remove initial slash
300  int nsSize = ns.size();
301  for (int i = 0; i < nsSize; ++i) {
302  // Mark if the last char was a slash
303  // so that two or more consecutive slashes
304  // could be converted to just one
305  // "a///b" -> "a/b"
306  if (ret.at(i) == QLatin1Char('/')) {
307  if (lastWasSlash) {
308  ret.remove(i, 1);
309  --nsSize;
310  } else {
311  lastWasSlash = true;
312  }
313  } else {
314  lastWasSlash = false;
315  }
316  }
317  return ret;
318 }
319 
320 QString DispatcherPrivate::normalizePath(const QString &path)
321 {
322  QString ret = path;
323  bool lastSlash = true;
324  int i = 0;
325  while (i < ret.size()) {
326  if (ret.at(i) == QLatin1Char('/')) {
327  if (lastSlash) {
328  ret.remove(i, 1);
329  continue;
330  }
331  lastSlash = true;
332  } else {
333  lastSlash = false;
334  }
335  ++i;
336  }
337 
338  if (ret.endsWith(QLatin1Char('/'))) {
339  ret.resize(ret.size() - 1);
340  }
341  return ret;
342 }
343 
344 void DispatcherPrivate::printActions() const
345 {
346  QVector<QStringList> table;
347 
348  auto keys = actions.keys();
349  std::sort(keys.begin(), keys.end());
350  for (const auto &key : keys) {
351  Action *action = actions.value(key).action;
352  QString path = key.toString();
353  if (!path.startsWith(QLatin1Char('/'))) {
354  path.prepend(QLatin1Char('/'));
355  }
356 
357  QStringList row;
358  row.append(path);
359  row.append(action->className());
360  row.append(action->name());
361  table.append(row);
362  }
363 
364  qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table, {
365  QLatin1String("Private"),
366  QLatin1String("Class"),
367  QLatin1String("Method")
368  },
369  QLatin1String("Loaded Private actions:")).constData();
370 }
371 
372 ActionList DispatcherPrivate::getContainers(const QString &ns) const
373 {
374  ActionList ret;
375 
376  if (ns.compare(u"/") != 0) {
377  int pos = ns.size();
378 // qDebug() << pos << ns.mid(0, pos);
379  while (pos > 0) {
380 // qDebug() << pos << ns.mid(0, pos);
381  ret.append(actionContainer.value(ns.mid(0, pos)));
382  pos = ns.lastIndexOf(QLatin1Char('/'), pos - 1);
383  }
384  }
385 // qDebug() << actionContainer.size() << rootActions;
386  ret.append(rootActions);
387 
388  return ret;
389 }
390 
391 Action *DispatcherPrivate::command2Action(Context *c, const QString &command, const QStringList &args) const
392 {
393  auto it = actions.constFind(command);
394  if (it != actions.constEnd()) {
395  return it.value().action;
396  }
397 
398  return invokeAsPath(c, command, args);
399 }
400 
401 Action *DispatcherPrivate::invokeAsPath(Context *c, const QString &relativePath, const QStringList &args) const
402 {
403  Q_UNUSED(args);
404  Q_Q(const Dispatcher);
405 
406  Action *ret;
407  QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
408 
409  int pos = path.lastIndexOf(QLatin1Char('/'));
410  int lastPos = path.size();
411  do {
412  if (pos == -1) {
413  ret = q->getAction(path, QString());
414  if (ret) {
415  return ret;
416  }
417  } else {
418  const QString name = path.mid(pos + 1, lastPos);
419  path = path.mid(0, pos);
420  ret = q->getAction(name, path);
421  if (ret) {
422  return ret;
423  }
424  }
425 
426  lastPos = pos;
427  pos = path.indexOf(QLatin1Char('/'), pos - 1);
428  } while (pos != -1);
429 
430  return nullptr;
431 }
432 
433 QString DispatcherPrivate::actionRel2Abs(Context *c, const QString &path)
434 {
435  QString ret;
436  if (path.startsWith(QLatin1Char('/'))) {
437  ret = path.mid(1);
438  return ret;
439  }
440 
441  const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
442  if (ns.isEmpty()) {
443  ret = path;
444  } else {
445  ret = ns + QLatin1Char('/') + path;
446  }
447  return ret;
448 }
449 
450 #include "moc_dispatcher.cpp"
bool dispatch(Context *c)
Definition: dispatcher.cpp:115
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString uriForAction(Action *action, const QStringList &captures) const
Definition: dispatcher.cpp:262
bool dispatch(Context *c)
Definition: action.h:81
void setupActions(const QVector< Controller *> &controllers, const QVector< DispatchType *> &dispatchers, bool printActions)
Definition: dispatcher.cpp:36
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:50
void append(const T &value)
QString & prepend(QChar ch)
QVector::reverse_iterator rend()
int size() const const
bool forward(Context *c, Component *component)
Definition: dispatcher.cpp:131
QString join(const QString &separator) const const
void prepareAction(Context *c)
Definition: dispatcher.cpp:152
QString & remove(int position, int n)
QVector< DispatchType * > dispatchers() const
Definition: dispatcher.cpp:290
The Cutelyst Component base class.
Definition: component.h:25
ActionList getActions(const QString &name, const QString &nameSpace) const
Definition: dispatcher.cpp:234
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isNull() const const
This class represents a Cutelyst Action.
Definition: action.h:34
virtual bool inUse()=0
The Cutelyst Context.
Definition: context.h:38
void resize(int size)
Cutelyst Controller base class
Definition: controller.h:89
void append(const T &value)
QMap< QString, Controller * > controllers() const
Definition: dispatcher.cpp:256
QString name() const
Definition: component.cpp:31
bool isEmpty() const const
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:471
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool _DISPATCH(Context *c)
Definition: controller.cpp:163
Controller * controller() const
Definition: action.cpp:90
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
bool execute(Component *code)
Definition: context.cpp:411
QStack< Component * > stack() const noexcept
Definition: context.cpp:224
QString mid(int position, int n) const const
Action * expandAction(const Context *c, Action *action) const
Definition: dispatcher.cpp:278
int count() const const
const QChar at(int position) const const
Action * getAction(const QString &name, const QString &nameSpace=QString()) const
Definition: dispatcher.cpp:203
void prepend(const T &value)
QVector::reverse_iterator rbegin()
Action * getActionByPath(const QString &path) const
Definition: dispatcher.cpp:220
QObject * parent() const const
Dispatcher(QObject *parent=nullptr)
Definition: dispatcher.cpp:24
int compare(const QString &other, Qt::CaseSensitivity cs) const const
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString className() const
Definition: action.cpp:84