Cutelyst  1.11.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
64  for (DispatchType *dispatch : dispatchers) {
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
121  for (DispatchType *dispatch : dispatchers) {
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  QString arg = path.mid(pos + 1);
209  args.prepend(Utils::decodePercentEncoding(&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 
258  for (Action *action : containers) {
259  if (action->name() == name) {
260  ret.prepend(action);
261  }
262  }
263  return ret;
264 }
265 
266 QMap<QString, Controller *> Dispatcher::controllers() const
267 {
268  Q_D(const Dispatcher);
269  return d->controllers;
270 }
271 
272 QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
273 {
274  Q_D(const Dispatcher);
275  QString ret;
276  for (DispatchType *dispatch : d->dispatchers) {
277  ret = dispatch->uriForAction(action, captures);
278  if (!ret.isNull()) {
279  if (ret.isEmpty()) {
280  ret = QStringLiteral("/");
281  }
282  break;
283  }
284  }
285  return ret;
286 }
287 
289 {
290  Q_D(const Dispatcher);
291  for (DispatchType *dispatch : d->dispatchers) {
292  Action *expandedAction = dispatch->expandAction(c, action);
293  if (expandedAction) {
294  return expandedAction;
295  }
296  }
297  return action;
298 }
299 
300 QVector<DispatchType *> Dispatcher::dispatchers() const
301 {
302  Q_D(const Dispatcher);
303  return d->dispatchers;
304 }
305 
306 QString DispatcherPrivate::cleanNamespace(const QString &ns)
307 {
308  QString ret = ns;
309  bool lastWasSlash = true; // remove initial slash
310  int nsSize = ns.size();
311  for (int i = 0; i < nsSize; ++i) {
312  // Mark if the last char was a slash
313  // so that two or more consecutive slashes
314  // could be converted to just one
315  // "a///b" -> "a/b"
316  if (ret.at(i) == QLatin1Char('/')) {
317  if (lastWasSlash) {
318  ret.remove(i, 1);
319  --nsSize;
320  } else {
321  lastWasSlash = true;
322  }
323  } else {
324  lastWasSlash = false;
325  }
326  }
327  return ret;
328 }
329 
330 QString DispatcherPrivate::normalizePath(const QString &path)
331 {
332  QString ret = path;
333  bool lastSlash = true;
334  int i = 0;
335  while (i < ret.size()) {
336  if (ret.at(i) == QLatin1Char('/')) {
337  if (lastSlash) {
338  ret.remove(i, 1);
339  continue;
340  }
341  lastSlash = true;
342  } else {
343  lastSlash = false;
344  }
345  ++i;
346  }
347 
348  if (ret.endsWith(QLatin1Char('/'))) {
349  ret.resize(ret.size() - 1);
350  }
351  return ret;
352 }
353 
354 void DispatcherPrivate::printActions() const
355 {
356  QVector<QStringList> table;
357 
358  QStringList keys = actions.keys();
359  keys.sort(Qt::CaseInsensitive);
360  for (const QString &key : keys) {
361  Action *action = actions.value(key);
362  QString path = key;
363  if (!path.startsWith(QLatin1Char('/'))) {
364  path.prepend(QLatin1Char('/'));
365  }
366 
367  QStringList row;
368  row.append(path);
369  row.append(action->className());
370  row.append(action->name());
371  table.append(row);
372  }
373 
374  qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table, {
375  QLatin1String("Private"),
376  QLatin1String("Class"),
377  QLatin1String("Method")
378  },
379  QLatin1String("Loaded Private actions:")).constData();
380 }
381 
382 ActionList DispatcherPrivate::getContainers(const QString &ns) const
383 {
384  ActionList ret;
385 
386  if (ns != QLatin1String("/")) {
387  int pos = ns.size();
388 // qDebug() << pos << ns.mid(0, pos);
389  while (pos > 0) {
390 // qDebug() << pos << ns.mid(0, pos);
391  ret.append(actionContainer.value(ns.mid(0, pos)));
392  pos = ns.lastIndexOf(QLatin1Char('/'), pos - 1);
393  }
394  }
395 // qDebug() << actionContainer.size() << rootActions;
396  ret.append(rootActions);
397 
398  return ret;
399 }
400 
401 Action *DispatcherPrivate::command2Action(Context *c, const QString &command, const QStringList &args) const
402 {
403  auto it = actions.constFind(command);
404  if (it != actions.constEnd()) {
405  return it.value();
406  }
407 
408  return invokeAsPath(c, command, args);
409 }
410 
411 Action *DispatcherPrivate::invokeAsPath(Context *c, const QString &relativePath, const QStringList &args) const
412 {
413  Q_Q(const Dispatcher);
414 
415  Action *ret;
416  QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
417 
418  int pos = path.lastIndexOf(QLatin1Char('/'));
419  int lastPos = path.size();
420  do {
421  if (pos == -1) {
422  ret = q->getAction(path, QString());
423  if (ret) {
424  return ret;
425  }
426  } else {
427  const QString name = path.mid(pos + 1, lastPos);
428  path = path.mid(0, pos);
429  ret = q->getAction(name, path);
430  if (ret) {
431  return ret;
432  }
433  }
434 
435  lastPos = pos;
436  pos = path.indexOf(QLatin1Char('/'), pos - 1);
437  } while (pos != -1);
438 
439  return nullptr;
440 }
441 
442 QString DispatcherPrivate::actionRel2Abs(Context *c, const QString &path)
443 {
444  QString ret;
445  if (path.startsWith(QLatin1Char('/'))) {
446  ret = path.mid(1);
447  return ret;
448  }
449 
450  const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
451  if (ns.isEmpty()) {
452  ret = path;
453  } else {
454  ret = ns + QLatin1Char('/') + path;
455  }
456  return ret;
457 }
458 
459 #include "moc_dispatcher.cpp"
bool dispatch(Context *c)
Definition: dispatcher.cpp:127
QVector< Action * > ActionList
Definition: action.h:162
QMap< QString, Controller * > controllers() const
Definition: dispatcher.cpp:266
bool dispatch(Context *c)
Definition: action.h:94
QString name() const
Definition: component.cpp:39
bool forward(Context *c, Component *component)
Definition: dispatcher.cpp:143
Controller * controller() const
Definition: action.cpp:105
void prepareAction(Context *c)
Definition: dispatcher.cpp:164
bool error() const
Returns true if an error was set.
Definition: context.cpp:64
QStack< Component * > stack() const
Definition: context.cpp:220
Action * getActionByPath(const QString &path) const
Definition: dispatcher.cpp:231
The Cutelyst Component base class.
Definition: component.h:38
Action * expandAction(Context *c, Action *action) const
Definition: dispatcher.cpp:288
This class represents a Cutelyst Action.
Definition: action.h:47
virtual bool inUse()=0
The Cutelyst Context.
Definition: context.h:50
Cutelyst Controller base class
Definition: controller.h:102
ActionList getActions(const QString &name, const QString &nameSpace) const
Definition: dispatcher.cpp:245
bool _DISPATCH(Context *c)
Definition: controller.cpp:162
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
bool execute(Component *code)
Definition: context.cpp:358
Action * getAction(const QString &name, const QString &nameSpace=QString()) const
Definition: dispatcher.cpp:215
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:422
QVector< DispatchType * > dispatchers() const
Definition: dispatcher.cpp:300
virtual Action * expandAction(Context *c, Action *action) const
Dispatcher(QObject *parent=nullptr)
Definition: dispatcher.cpp:37
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
QString className() const
Definition: action.cpp:99
QString uriForAction(Action *action, const QStringList &captures) const
Definition: dispatcher.cpp:272
virtual QString uriForAction(Action *action, const QStringList &captures) const =0
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
Definition: dispatcher.cpp:49