Fawkes API Fawkes Development Version
blackboard-rest-api.cpp
1
2/***************************************************************************
3 * blackboard-rest-api.cpp - Blackboard REST API
4 *
5 * Created: Mon Mar 26 23:27:42 2018
6 * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7 ****************************************************************************/
8
9/* This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Library General Public License for more details.
18 *
19 * Read the full text in the LICENSE.GPL file in the doc directory.
20 */
21
22#include "blackboard-rest-api.h"
23
24#include <core/threading/mutex_locker.h>
25#include <interface/interface.h>
26#include <interface/message.h>
27#include <rapidjson/document.h>
28#include <utils/time/wait.h>
29#include <webview/rest_api_manager.h>
30
31#include <set>
32
33using namespace fawkes;
34
35/** @class BlackboardRestApi "skiller-rest-api.h"
36 * REST API backend for the blackboard.
37 * @author Tim Niemueller
38 */
39
40/** Constructor. */
41BlackboardRestApi::BlackboardRestApi() : Thread("BlackboardRestApi", Thread::OPMODE_WAITFORWAKEUP)
42{
43}
44
45/** Destructor. */
47{
48}
49
50void
52{
53 rest_api_ = new WebviewRestApi("blackboard", logger);
55 WebRequest::METHOD_GET, "/interfaces", std::bind(&BlackboardRestApi::cb_list_interfaces, this));
56 rest_api_->add_handler<InterfaceData>(WebRequest::METHOD_GET,
57 "/interfaces/{type}/{id+}/data",
58 std::bind(&BlackboardRestApi::cb_get_interface_data,
59 this,
60 std::placeholders::_1));
61 rest_api_->add_handler<::InterfaceInfo>(WebRequest::METHOD_GET,
62 "/interfaces/{type}/{id+}",
63 std::bind(&BlackboardRestApi::cb_get_interface_info,
64 this,
65 std::placeholders::_1));
66 rest_api_->add_handler<BlackboardGraph>(WebRequest::METHOD_GET,
67 "/graph",
68 std::bind(&BlackboardRestApi::cb_get_graph, this));
70}
71
72void
74{
76 delete rest_api_;
77}
78
79void
81{
82}
83
84std::vector<std::shared_ptr<InterfaceFieldType>>
85BlackboardRestApi::gen_fields(fawkes::InterfaceFieldIterator begin,
87{
88 std::vector<std::shared_ptr<InterfaceFieldType>> rv;
89 for (auto i = begin; i != end; ++i) {
90 std::shared_ptr<InterfaceFieldType> ft = std::make_shared<InterfaceFieldType>();
91 ft->set_name(i.get_name());
92 ft->set_type(i.get_typename());
93 ft->set_is_array(i.get_type() != IFT_STRING && i.get_length() > 1);
94 if (i.is_enum()) {
95 std::list<const char *> enum_values = i.get_enum_valuenames();
96 ft->set_enums(std::vector<std::string>{std::begin(enum_values), std::end(enum_values)});
97 }
98 rv.push_back(ft);
99 }
100 return rv;
101}
102
104BlackboardRestApi::gen_interface_info(const fawkes::InterfaceInfo &ii)
105{
106 ::InterfaceInfo info;
107 info.set_kind("InterfaceInfo");
108 info.set_apiVersion(::InterfaceInfo::api_version());
109 info.set_id(ii.id());
110 info.set_type(ii.type());
111 info.set_hash(ii.hash_printable());
112 if (ii.has_writer()) {
113 info.set_writer(ii.writer());
114 }
115 std::list<std::string> readers = ii.readers();
116 info.set_readers(std::vector<std::string>{std::begin(readers), std::end(readers)});
117
118 if (type_info_cache_.find(ii.type()) != type_info_cache_.end()) {
119 info.set_fields(type_info_cache_[ii.type()].first);
120 info.set_message_types(type_info_cache_[ii.type()].second);
121 } else {
122 Interface *iface = blackboard->open_for_reading(ii.type(), ii.id());
123 iface->read();
124
125 std::vector<std::shared_ptr<InterfaceFieldType>> fields =
126 gen_fields(iface->fields(), iface->fields_end());
127
128 info.set_fields(fields);
129
130 std::vector<std::shared_ptr<InterfaceMessageType>> message_types;
131
132 std::list<const char *> message_type_names = iface->get_message_types();
133 for (auto mt : message_type_names) {
134 Message * msg = iface->create_message(mt);
135 std::shared_ptr<InterfaceMessageType> m = std::make_shared<InterfaceMessageType>();
136 m->set_name(mt);
137 m->set_fields(gen_fields(msg->fields(), msg->fields_end()));
138 message_types.push_back(m);
139 delete msg;
140 }
141 blackboard->close(iface);
142
143 info.set_message_types(message_types);
144 type_info_cache_[ii.type()] = std::make_pair(fields, message_types);
145 }
146 return info;
147}
148
149#define FIELD_ARRAY_CASE(TYPE, type, ctype) \
150 case IFT_##TYPE: { \
151 const ctype *values = i.get_##type##s(); \
152 for (unsigned int j = 0; j < i.get_length(); ++j) { \
153 value.PushBack(rapidjson::Value{values[j]}.Move(), allocator); \
154 } \
155 break; \
156 }
157
158static rapidjson::Value
159gen_field_value(fawkes::InterfaceFieldIterator & i,
160 fawkes::Interface * iface,
161 rapidjson::Document::AllocatorType &allocator)
162{
163 rapidjson::Value value;
164
165 if (i.get_length() > 1) {
166 if (i.get_type() == IFT_STRING) {
167 value.SetString(std::string(i.get_string()), allocator);
168 } else {
169 value.SetArray();
170 value.Reserve(i.get_length(), allocator);
171
172 switch (i.get_type()) {
173 FIELD_ARRAY_CASE(BOOL, bool, bool);
174 FIELD_ARRAY_CASE(INT8, int8, int8_t);
175 FIELD_ARRAY_CASE(UINT8, uint8, uint8_t);
176 FIELD_ARRAY_CASE(INT16, int16, int16_t);
177 FIELD_ARRAY_CASE(UINT16, uint16, uint16_t);
178 FIELD_ARRAY_CASE(INT32, int32, int32_t);
179 FIELD_ARRAY_CASE(UINT32, uint32, uint32_t);
180 FIELD_ARRAY_CASE(INT64, int64, int64_t);
181 FIELD_ARRAY_CASE(UINT64, uint64, uint64_t);
182 FIELD_ARRAY_CASE(FLOAT, float, float);
183 FIELD_ARRAY_CASE(DOUBLE, double, double);
184 FIELD_ARRAY_CASE(BYTE, byte, uint8_t);
185
186 case IFT_ENUM: {
187 const int32_t *values = i.get_enums();
188 for (unsigned int j = 0; j < i.get_length(); ++j) {
189 value.PushBack(
190 rapidjson::Value{iface->enum_tostring(i.get_typename(), values[j]), allocator}.Move(),
191 allocator);
192 }
193 break;
194 }
195
196 case IFT_STRING: break; // handled above
197 }
198 }
199 } else {
200 switch (i.get_type()) {
201 case IFT_BOOL: value.SetBool(i.get_bool()); break;
202 case IFT_INT8: value.SetInt(i.get_int8()); break;
203 case IFT_UINT8: value.SetUint(i.get_uint8()); break;
204 case IFT_INT16: value.SetInt(i.get_int16()); break;
205 case IFT_UINT16: value.SetUint(i.get_uint16()); break;
206 case IFT_INT32: value.SetInt(i.get_int32()); break;
207 case IFT_UINT32: value.SetUint(i.get_uint32()); break;
208 case IFT_INT64: value.SetInt64(i.get_int64()); break;
209 case IFT_UINT64: value.SetUint64(i.get_uint64()); break;
210 case IFT_FLOAT: value.SetFloat(i.get_float()); break;
211 case IFT_DOUBLE: value.SetDouble(i.get_double()); break;
212 case IFT_BYTE: value.SetUint(i.get_byte()); break;
213 case IFT_STRING: [[fallthrough]];
214 case IFT_ENUM: value.SetString(std::string(i.get_value_string()), allocator);
215 }
216 }
217 return value;
218}
219
221BlackboardRestApi::gen_interface_data(Interface *iface, bool pretty)
222{
223 InterfaceData data;
224 data.set_kind("InterfaceData");
226 data.set_type(iface->type());
227 data.set_id(iface->id());
228 if (iface->has_writer()) {
229 data.set_writer(iface->writer());
230 }
231 std::list<std::string> readers = iface->readers();
232 data.set_readers(std::vector<std::string>{std::begin(readers), std::end(readers)});
233 data.set_timestamp(iface->timestamp()->str());
234
235 // Generate data as JSON document
236 std::shared_ptr<rapidjson::Document> d = std::make_shared<rapidjson::Document>();
237 rapidjson::Document::AllocatorType & allocator = d->GetAllocator();
238 d->SetObject();
239
240 for (auto i = iface->fields(); i != iface->fields_end(); ++i) {
241 rapidjson::Value name(i.get_name(), allocator);
242 d->AddMember(name.Move(), gen_field_value(i, iface, allocator).Move(), allocator);
243 }
244 data.set_data(d);
245
246 return data;
247}
248
250BlackboardRestApi::cb_list_interfaces()
251{
253
254 std::unique_ptr<InterfaceInfoList> ifls{blackboard->list_all()};
255
256 for (const auto &ii : *ifls) {
257 rv.push_back(gen_interface_info(ii));
258 }
259
260 return rv;
261}
262
264BlackboardRestApi::cb_get_interface_info(WebviewRestParams &params)
265{
266 if (params.path_arg("type").find_first_of("*?") != std::string::npos) {
267 throw WebviewRestException(WebReply::HTTP_BAD_REQUEST, "Type may not contain any of [*?].");
268 }
269 if (params.path_arg("id").find_first_of("*?") != std::string::npos) {
270 throw WebviewRestException(WebReply::HTTP_BAD_REQUEST, "ID may not contain any of [*?].");
271 }
272
273 std::unique_ptr<InterfaceInfoList> ifls{
274 blackboard->list(params.path_arg("type").c_str(), params.path_arg("id").c_str())};
275
276 if (ifls->size() < 1) {
277 throw WebviewRestException(WebReply::HTTP_NOT_FOUND,
278 "Interface %s:%s not found",
279 params.path_arg("type").c_str(),
280 params.path_arg("id").c_str());
281 }
282 return gen_interface_info(ifls->front());
283}
284
286BlackboardRestApi::cb_get_interface_data(WebviewRestParams &params)
287{
288 bool pretty = params.has_query_arg("pretty");
289 params.set_pretty_json(pretty);
290
291 if (params.path_arg("type").find_first_of("*?") != std::string::npos) {
292 throw WebviewRestException(WebReply::HTTP_BAD_REQUEST, "Type may not contain any of [*?].");
293 }
294 if (params.path_arg("id").find_first_of("*?") != std::string::npos) {
295 throw WebviewRestException(WebReply::HTTP_BAD_REQUEST, "ID may not contain any of [*?].");
296 }
297
298 std::unique_ptr<InterfaceInfoList> ifls{
299 blackboard->list(params.path_arg("type").c_str(), params.path_arg("id").c_str())};
300 if (ifls->size() == 0) {
301 throw WebviewRestException(WebReply::HTTP_NOT_FOUND,
302 "Interface %s::%s: is currently not available",
303 params.path_arg("type").c_str(),
304 params.path_arg("id").c_str());
305 }
306
307 Interface *iface = nullptr;
308 try {
309 iface =
310 blackboard->open_for_reading(params.path_arg("type").c_str(), params.path_arg("id").c_str());
311 iface->read();
312 } catch (Exception &e) {
313 throw WebviewRestException(WebReply::HTTP_NOT_FOUND,
314 "Failed to open %s::%s: %s",
315 params.path_arg("type").c_str(),
316 params.path_arg("id").c_str(),
318 }
319
320 try {
321 InterfaceData d{gen_interface_data(iface, pretty)};
322 blackboard->close(iface);
323 return d;
324 } catch (Exception &e) {
325 blackboard->close(iface);
326 throw WebviewRestException(WebReply::HTTP_NOT_FOUND,
327 "Failed to read %s:%s: %s",
328 params.path_arg("type").c_str(),
329 params.path_arg("id").c_str(),
331 }
332}
333
334std::string
335BlackboardRestApi::generate_graph(const std::string &for_owner)
336{
338 iil->sort();
339
340 std::stringstream mstream;
341 mstream << "digraph bbmap {" << std::endl << " graph [fontsize=12,rankdir=LR];" << std::endl;
342
343 std::set<std::string> owners;
344
345 InterfaceInfoList::iterator ii;
346 for (ii = iil->begin(); ii != iil->end(); ++ii) {
347 const std::list<std::string> readers = ii->readers();
348
349 if (for_owner == "" || ii->writer() == for_owner
350 || std::find_if(readers.begin(), readers.end(), [&for_owner](const std::string &o) -> bool {
351 return for_owner == o;
352 }) != readers.end()) {
353 if (ii->has_writer()) {
354 const std::string writer = ii->writer();
355 if (!writer.empty())
356 owners.insert(writer);
357 }
358 std::list<std::string>::const_iterator r;
359 for (r = readers.begin(); r != readers.end(); ++r) {
360 owners.insert(*r);
361 }
362 }
363 }
364
365 mstream << " node [fontsize=12 shape=box width=4 margin=0.05];" << std::endl
366 << " { rank=same; " << std::endl;
367 std::set<std::string>::iterator i;
368 for (ii = iil->begin(); ii != iil->end(); ++ii) {
369 const std::list<std::string> readers = ii->readers();
370 if (for_owner == "" || ii->writer() == for_owner
371 || std::find_if(readers.begin(), readers.end(), [&for_owner](const std::string &o) -> bool {
372 return for_owner == o;
373 }) != readers.end()) {
374 mstream << " \"" << ii->type() << "::" << ii->id() << "\""
375 << " [href=\"/blackboard/view/" << ii->type() << "::" << ii->id() << "\"";
376
377 if (!ii->has_writer()) {
378 mstream << " color=red";
379 } else if (ii->writer().empty()) {
380 mstream << " color=purple";
381 }
382 mstream << "];" << std::endl;
383 }
384 }
385 mstream << " }" << std::endl;
386
387 mstream << " node [fontsize=12 shape=octagon width=3];" << std::endl;
388 for (i = owners.begin(); i != owners.end(); ++i) {
389 mstream << " \"" << *i << "\""
390 << " [href=\"/blackboard/graph/" << *i << "\"];" << std::endl;
391 }
392
393 for (ii = iil->begin(); ii != iil->end(); ++ii) {
394 const std::list<std::string> readers = ii->readers();
395 if (for_owner == "" || ii->writer() == for_owner
396 || std::find_if(readers.begin(), readers.end(), [&for_owner](const std::string &o) -> bool {
397 return for_owner == o;
398 }) != readers.end()) {
399 std::list<std::string> quoted_readers;
400 std::for_each(readers.begin(), readers.end(), [&quoted_readers](const std::string &r) {
401 quoted_readers.push_back(std::string("\"") + r + "\"");
402 });
403 std::string quoted_readers_s = str_join(quoted_readers, ' ');
404 mstream << " \"" << ii->type() << "::" << ii->id() << "\" -> { " << quoted_readers_s
405 << " } [style=dashed arrowhead=dot arrowsize=0.5 dir=both];" << std::endl;
406
407 if (ii->has_writer()) {
408 mstream << " \"" << (ii->writer().empty() ? "???" : ii->writer()) << "\" -> \""
409 << ii->type() << "::" << ii->id() << "\""
410 << (ii->writer().empty() ? " [color=purple]" : " [color=\"#008800\"]") << ";"
411 << std::endl;
412 }
413 }
414 }
415
416 delete iil;
417
418 mstream << "}";
419 return mstream.str();
420}
421
423BlackboardRestApi::cb_get_graph()
424{
425 try {
426 BlackboardGraph graph;
427 graph.set_kind("TransformsGraph");
429 graph.set_dotgraph(generate_graph());
430 return graph;
431 } catch (Exception &e) {
432 throw WebviewRestException(WebReply::HTTP_INTERNAL_SERVER_ERROR,
433 "Failed to retrieve blackboard graph: %s",
435 }
436}
BlackboardGraph representation for JSON transfer.
void set_kind(const std::string &kind)
Set kind value.
static std::string api_version()
Get version of implemented API.
void set_apiVersion(const std::string &apiVersion)
Set apiVersion value.
void set_dotgraph(const std::string &dotgraph)
Set dotgraph value.
virtual void loop()
Code to execute in the thread.
~BlackboardRestApi()
Destructor.
virtual void finalize()
Finalize the thread.
virtual void init()
Initialize the thread.
BlackboardRestApi()
Constructor.
InterfaceData representation for JSON transfer.
Definition: InterfaceData.h:28
void set_timestamp(const std::string &timestamp)
Set timestamp value.
static std::string api_version()
Get version of implemented API.
Definition: InterfaceData.h:48
void set_kind(const std::string &kind)
Set kind value.
void set_readers(const std::vector< std::string > &readers)
Set readers value.
void set_data(const std::shared_ptr< rapidjson::Document > &data)
Set data value.
void set_apiVersion(const std::string &apiVersion)
Set apiVersion value.
void set_type(const std::string &type)
Set type value.
void set_id(const std::string &id)
Set id value.
void set_writer(const std::string &writer)
Set writer value.
static std::string api_version()
Get version of implemented API.
Definition: InterfaceInfo.h:51
Container to return array via REST.
Definition: rest_array.h:36
void push_back(M &m)
Add item at the back of the container.
Definition: rest_array.h:123
BlackBoard * blackboard
This is the BlackBoard instance you can use to interact with the BlackBoard.
Definition: blackboard.h:44
virtual Interface * open_for_reading(const char *interface_type, const char *identifier, const char *owner=NULL)=0
Open interface for reading.
virtual InterfaceInfoList * list_all()=0
Get list of all currently existing interfaces.
virtual InterfaceInfoList * list(const char *type_pattern, const char *id_pattern)=0
Get list of interfaces matching type and ID patterns.
virtual void close(Interface *interface)=0
Close interface.
Base class for exceptions in Fawkes.
Definition: exception.h:36
virtual const char * what_no_backtrace() const noexcept
Get primary string (does not implicitly print the back trace).
Definition: exception.cpp:663
Interface field iterator.
float get_float(unsigned int index=0) const
Get value of current field as float.
int16_t get_int16(unsigned int index=0) const
Get value of current field as integer.
int8_t get_int8(unsigned int index=0) const
Get value of current field as integer.
int32_t get_int32(unsigned int index=0) const
Get value of current field as integer.
size_t get_length() const
Get length of current field.
int64_t get_int64(unsigned int index=0) const
Get value of current field as integer.
uint64_t get_uint64(unsigned int index=0) const
Get value of current field as unsigned integer.
uint16_t get_uint16(unsigned int index=0) const
Get value of current field as unsigned integer.
double get_double(unsigned int index=0) const
Get value of current field as double.
int32_t * get_enums() const
Get value of current enum field as integer array.
uint32_t get_uint32(unsigned int index=0) const
Get value of current field as unsigned integer.
interface_fieldtype_t get_type() const
Get type of current field.
const char * get_name() const
Get name of current field.
const char * get_string() const
Get value of current field as string.
uint8_t get_byte(unsigned int index=0) const
Get value of current field as byte.
uint8_t get_uint8(unsigned int index=0) const
Get value of current field as unsigned integer.
bool get_bool(unsigned int index=0) const
Get value of current field as bool.
const char * get_value_string(const char *array_sep=", ")
Get value of current field as string.
const char * get_typename() const
Get type of current field as string.
Interface information list.
Interface info.
const std::list< std::string > & readers() const
Get readers of interface.
bool has_writer() const
Check if there is a writer.
const char * type() const
Get interface type.
const char * id() const
Get interface ID.
const std::string & writer() const
Get name of writer on interface.
std::string hash_printable() const
Get interface version hash in printable format.
Base class for all Fawkes BlackBoard interfaces.
Definition: interface.h:80
std::list< std::string > readers() const
Get owner names of reading interface instances.
Definition: interface.cpp:895
const char * type() const
Get type of interface.
Definition: interface.cpp:652
std::string writer() const
Get owner name of writing interface instance.
Definition: interface.cpp:886
const Time * timestamp() const
Get timestamp of last write.
Definition: interface.cpp:714
InterfaceFieldIterator fields_end()
Invalid iterator.
Definition: interface.cpp:1240
virtual Message * create_message(const char *type) const =0
Create message based on type name.
const char * id() const
Get identifier of interface.
Definition: interface.cpp:661
InterfaceFieldIterator fields()
Get iterator over all fields of this interface instance.
Definition: interface.cpp:1231
virtual const char * enum_tostring(const char *enumtype, int val) const =0
Convert arbitrary enum value to string.
void read()
Read from BlackBoard into local copy.
Definition: interface.cpp:479
std::list< const char * > get_message_types()
Obtain a list of textual representations of the message types available for this interface.
Definition: interface.cpp:408
bool has_writer() const
Check if there is a writer for the interface.
Definition: interface.cpp:848
Logger * logger
This is the Logger member used to access the logger.
Definition: logging.h:41
Base class for all messages passed through interfaces in Fawkes BlackBoard.
Definition: message.h:44
InterfaceFieldIterator fields()
Get iterator over all fields of this interface instance.
Definition: message.cpp:390
InterfaceFieldIterator fields_end()
Invalid iterator.
Definition: message.cpp:399
Thread class encapsulation of pthreads.
Definition: thread.h:46
const char * name() const
Get name of thread.
Definition: thread.h:100
const char * str(bool utc=false) const
Output function.
Definition: time.cpp:790
WebviewRestApiManager * webview_rest_api_manager
Webview REST API manager.
Definition: webview.h:55
void unregister_api(WebviewRestApi *api)
Remove a request processor.
void register_api(WebviewRestApi *api)
Add a REST API.
Webview REST API component.
Definition: rest_api.h:221
void add_handler(WebRequest::Method method, std::string path, Handler handler)
Add handler function.
Definition: rest_api.cpp:85
REST processing exception.
Definition: rest_api.h:71
REST parameters to pass to handlers.
Definition: rest_api.h:125
bool has_query_arg(const std::string &what)
Check if query argument is set.
Definition: rest_api.h:174
std::string path_arg(const std::string &what)
Get a path argument.
Definition: rest_api.h:142
void set_pretty_json(bool pretty)
Enable or disable pretty printed results.
Definition: rest_api.h:194
Fawkes library namespace.
std::string str_join(const InputIterator &first, const InputIterator &last, char delim='/')
Join list of strings string using given delimiter.
Definition: string_split.h:139
@ IFT_INT8
8 bit integer field
Definition: types.h:38
@ IFT_UINT32
32 bit unsigned integer field
Definition: types.h:43
@ IFT_FLOAT
float field
Definition: types.h:46
@ IFT_BYTE
byte field, alias for uint8
Definition: types.h:49
@ IFT_UINT64
64 bit unsigned integer field
Definition: types.h:45
@ IFT_UINT16
16 bit unsigned integer field
Definition: types.h:41
@ IFT_INT32
32 bit integer field
Definition: types.h:42
@ IFT_INT64
64 bit integer field
Definition: types.h:44
@ IFT_DOUBLE
double field
Definition: types.h:47
@ IFT_INT16
16 bit integer field
Definition: types.h:40
@ IFT_STRING
string field
Definition: types.h:48
@ IFT_BOOL
boolean field
Definition: types.h:37
@ IFT_ENUM
field with interface specific enum type
Definition: types.h:50
@ IFT_UINT8
8 bit unsigned integer field
Definition: types.h:39