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