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