Fawkes API  Fawkes Development Version
clips_env_manager.cpp
1 
2 /***************************************************************************
3  * clips_env_manager.cpp - CLIPS environment manager
4  *
5  * Created: Thu Aug 15 18:57:58 2013
6  * Copyright 2006-2014 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. A runtime exception applies to
13  * this software (see LICENSE.GPL_WRE file mentioned below for details).
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
21  */
22 
23 #include <plugins/clips/aspect/clips_env_manager.h>
24 #include <plugins/clips/aspect/clips_feature.h>
25 #include <logging/logger.h>
26 #include <utils/time/time.h>
27 
28 #include <cstring>
29 
30 extern "C" {
31 #include <clips/clips.h>
32 }
33 
34 namespace fawkes {
35 #if 0 /* just to make Emacs auto-indent happy */
36 }
37 #endif
38 
39 
40 #define ROUTER_NAME "fawkeslog"
41 
42 /// @cond INTERNALS
43 class CLIPSLogger
44 {
45  public:
46  CLIPSLogger(Logger *logger, const char *component = NULL)
47  {
48  logger_ = logger;
49  if (component) {
50  component_ = strdup(component);
51  } else {
52  component_ = NULL;
53  }
54  }
55 
56  ~CLIPSLogger()
57  {
58  if (component_) {
59  free(component_);
60  }
61  }
62 
63  void log(const char *logical_name, const char *str)
64  {
65  if (strcmp(str, "\n") == 0) {
66  if (strcmp(logical_name, "debug") == 0 || strcmp(logical_name, "logdebug") == 0 ||
67  strcmp(logical_name, WTRACE) == 0)
68  {
69  logger_->log_debug(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
70  } else if (strcmp(logical_name, "warn") == 0 || strcmp(logical_name, "logwarn") == 0 ||
71  strcmp(logical_name, WWARNING) == 0)
72  {
73  logger_->log_warn(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
74  } else if (strcmp(logical_name, "error") == 0 || strcmp(logical_name, "logerror") == 0 ||
75  strcmp(logical_name, WERROR) == 0)
76  {
77  logger_->log_error(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
78  } else if (strcmp(logical_name, WDIALOG) == 0) {
79  // ignored
80  } else {
81  logger_->log_info(component_ ? component_ : "CLIPS", "%s", buffer_.c_str());
82  }
83 
84  buffer_.clear();
85  } else {
86  buffer_ += str;
87  }
88  }
89 
90  private:
91  Logger *logger_;
92  char *component_;
93  std::string buffer_;
94 };
95 
96 class CLIPSContextMaintainer {
97  public:
98  CLIPSContextMaintainer(Logger *logger, const char *log_component_name)
99  {
100  this->logger = new CLIPSLogger(logger, log_component_name);
101  }
102 
103  ~CLIPSContextMaintainer()
104  {
105  delete logger;
106  }
107 
108  public:
109  CLIPSLogger *logger;
110 };
111 
112 
113 static int
114 log_router_query(void *env, char *logical_name)
115 {
116  if (strcmp(logical_name, "l") == 0) return TRUE;
117  if (strcmp(logical_name, "info") == 0) return TRUE;
118  if (strcmp(logical_name, "debug") == 0) return TRUE;
119  if (strcmp(logical_name, "warn") == 0) return TRUE;
120  if (strcmp(logical_name, "error") == 0) return TRUE;
121  if (strcmp(logical_name, "loginfo") == 0) return TRUE;
122  if (strcmp(logical_name, "logdebug") == 0) return TRUE;
123  if (strcmp(logical_name, "logwarn") == 0) return TRUE;
124  if (strcmp(logical_name, "logerror") == 0) return TRUE;
125  if (strcmp(logical_name, "stdout") == 0) return TRUE;
126  if (strcmp(logical_name, WTRACE) == 0) return TRUE;
127  if (strcmp(logical_name, WDIALOG) == 0) return TRUE;
128  if (strcmp(logical_name, WWARNING) == 0) return TRUE;
129  if (strcmp(logical_name, WERROR) == 0) return TRUE;
130  if (strcmp(logical_name, WDISPLAY) == 0) return TRUE;
131  return FALSE;
132 }
133 
134 static int
135 log_router_print(void *env, char *logical_name, char *str)
136 {
137  void *rc = GetEnvironmentRouterContext(env);
138  CLIPSLogger *logger = static_cast<CLIPSLogger *>(rc);
139  logger->log(logical_name, str);
140  return TRUE;
141 }
142 
143 static int
144 log_router_exit(void *env, int exit_code)
145 {
146  return TRUE;
147 }
148 
149 /// @endcond
150 
151 
152 /** @class CLIPSEnvManager <plugins/clips/aspect/clips_env_manager.h>
153  * CLIPS environment manager.
154  * The CLIPS environment manager creates and maintains CLIPS
155  * environments, registers features and provides them to the CLIPS
156  * environments, and allows access to any and all CLIPS environments.
157  * @author Tim Niemueller
158  */
159 
160 /** Constructor.
161  * @param logger logger to log messages from created environments
162  * @param clock clock to get time from for (now)
163  * @param clips_dir path where to look for CLIPS files
164  */
165 CLIPSEnvManager::CLIPSEnvManager(Logger *logger, Clock *clock, std::string &clips_dir)
166 {
167  logger_ = logger;
168  clock_ = clock;
169  clips_dir_ = clips_dir;
170 }
171 
172 /** Destructor. */
174 {
175 }
176 
177 
178 /** Create a new environment.
179  * This function creates a new plain environment and sets up logging etc.
180  * @param log_component_name prefix for log entries
181  * @return readily initialized CLIPS environment
182  */
184 CLIPSEnvManager::new_env(const std::string &log_component_name)
185 {
186  // CLIPS overwrites the SIGINT handler, restore it after
187  // initializing the environment
188  struct sigaction oldact;
189  if (sigaction(SIGINT, NULL, &oldact) == 0) {
190  LockPtr<CLIPS::Environment> clips(new CLIPS::Environment(),
191  /* recursive mutex */ true);
192 
193  // by default be silent
194  clips->unwatch("all");
195 
196  CLIPSContextMaintainer *cm =
197  new CLIPSContextMaintainer(logger_, log_component_name.c_str());
198 
199  void *env = clips->cobj();
200 
201  SetEnvironmentContext(env, cm);
202 
203  EnvAddRouterWithContext(env, (char *)ROUTER_NAME,
204  /* exclusive */ 30,
205  log_router_query,
206  log_router_print,
207  /* getc */ NULL,
208  /* ungetc */ NULL,
209  log_router_exit,
210  cm->logger);
211 
212  // restore old action
213  sigaction(SIGINT, &oldact, NULL);
214 
215  return clips;
216  } else {
217  throw Exception("CLIPS: Unable to backup "
218  "SIGINT sigaction for restoration.");
219  }
220 }
221 
222 
223 /** Create a new environment.
224  * The environment is registered internally under the specified name.
225  * It must be destroyed when done with it. Only a single environment
226  * can be created for a particular environment name.
227  * @param env_name name by which to register environment
228  * @param log_component_name prefix for log entries
229  * @return readily initialized CLIPS environment
230  */
232 CLIPSEnvManager::create_env(const std::string &env_name, const std::string &log_component_name)
233 {
235  if (envs_.find(env_name) != envs_.end()) {
236  throw Exception("CLIPS environment '%s' already exists", env_name.c_str());
237  }
238 
239  clips = new_env(log_component_name);
240 
241  if (clips) {
242  envs_[env_name].env = clips;
243 
244  // add generic functions
245  add_functions(env_name, clips);
246 
247  // assert all currently available features to environment
248  assert_features(clips, true);
249 
250  guarded_load(env_name, clips_dir_ + "utils.clp");
251  guarded_load(env_name, clips_dir_ + "time.clp");
252  guarded_load(env_name, clips_dir_ + "path.clp");
253 
254  clips->evaluate("(path-add \"" + clips_dir_ + "\")");
255 
256  return clips;
257  } else {
258  throw Exception("Failed to initialize CLIPS environment '%s'", env_name.c_str());
259  }
260 }
261 
262 /** Destroy the named environment.
263  * Only ever destroy environments which you have created yourself.
264  * @param env_name name of the environment to destroy
265  */
266 void
267 CLIPSEnvManager::destroy_env(const std::string &env_name)
268 {
269  if (envs_.find(env_name) != envs_.end()) {
270  void *env = envs_[env_name].env->cobj();
271  CLIPSContextMaintainer *cm =
272  static_cast<CLIPSContextMaintainer *>(GetEnvironmentContext(env));
273 
274  EnvDeleteRouter(env, (char *)ROUTER_NAME);
275  SetEnvironmentContext(env, NULL);
276  delete cm;
277 
278  for (auto feat : envs_[env_name].req_feat) {
279  if (features_.find(feat) != features_.end()) {
280  features_[feat]->clips_context_destroyed(env_name);
281  }
282  }
283 
284  envs_.erase(env_name);
285  }
286 }
287 
288 
289 /** Get map of environments.
290  * @return map from environment name to environment lock ptr
291  */
292 std::map<std::string, LockPtr<CLIPS::Environment>>
294 {
295  std::map<std::string, LockPtr<CLIPS::Environment>> rv;
296  for (auto envd : envs_) {
297  rv[envd.first] = envd.second.env;
298  }
299  return rv;
300 }
301 
302 
303 CLIPS::Value
304 CLIPSEnvManager::clips_request_feature(std::string env_name, std::string feature_name)
305 {
306  bool rv = true;
307 
308  logger_->log_debug("ClipsEnvManager", "Environment %s requests feature %s",
309  env_name.c_str(), feature_name.c_str());
310 
311  if (envs_.find(env_name) == envs_.end()) {
312  logger_->log_warn("ClipsEnvManager", "Feature %s request from non-existent environment %s",
313  feature_name.c_str(), env_name.c_str());
314  return CLIPS::Value("FALSE", CLIPS::TYPE_SYMBOL);
315  }
316  if (features_.find(feature_name) == features_.end()) {
317  logger_->log_warn("ClipsEnvManager", "Environment requested unavailable feature %s",
318  feature_name.c_str());
319  return CLIPS::Value("FALSE", CLIPS::TYPE_SYMBOL);
320  }
321 
322  ClipsEnvData &envd = envs_[env_name];
323  if (std::binary_search(envd.req_feat.begin(), envd.req_feat.end(), feature_name)) {
324  logger_->log_warn("ClipsEnvManager", "Environment %s requested feature %s *again*",
325  env_name.c_str(), feature_name.c_str());
326  return CLIPS::Value("TRUE", CLIPS::TYPE_SYMBOL);
327  }
328 
329  envd.env.lock();
330  features_[feature_name]->clips_context_init(env_name, envd.env);
331  envd.req_feat.push_back(feature_name);
332  envd.req_feat.sort();
333 
334  // deffact so it survives a reset
335  std::string deffacts = "(deffacts ff-features-loaded";
336 
337  for (auto feat : envd.req_feat) {
338  deffacts += " (ff-feature-loaded " + feat + ")";
339  }
340  deffacts += ")";
341 
342  envd.env->assert_fact_f("(ff-feature-loaded %s)", feature_name.c_str());
343 
344  if (! envd.env->build(deffacts)) {
345  logger_->log_warn("ClipsEnvManager", "Failed to build deffacts ff-features-loaded "
346  "for %s", env_name.c_str());
347  rv = false;
348  }
349  envd.env.unlock();
350 
351  return CLIPS::Value(rv ? "TRUE" : "FALSE", CLIPS::TYPE_SYMBOL);
352 }
353 
354 
355 CLIPS::Values
356 CLIPSEnvManager::clips_now()
357 {
358  CLIPS::Values rv;
359  fawkes::Time now(clock_);
360  rv.push_back(now.get_sec());
361  rv.push_back(now.get_usec());
362  return rv;
363 }
364 
365 
366 void
367 CLIPSEnvManager::add_functions(const std::string &env_name, LockPtr<CLIPS::Environment> &clips)
368 {
369  clips->add_function("ff-feature-request", sigc::slot<CLIPS::Value, std::string>(sigc::bind<0>(sigc::mem_fun(*this, &CLIPSEnvManager::clips_request_feature), env_name)));
370  clips->add_function("now", sigc::slot<CLIPS::Values>(sigc::mem_fun( *this, &CLIPSEnvManager::clips_now)));
371 }
372 
373 void
374 CLIPSEnvManager::assert_features(LockPtr<CLIPS::Environment> &clips, bool immediate_assert)
375 {
376 
377  // deffact so it survives a reset
378  std::string deffacts = "(deffacts ff-features-available";
379 
380  for (auto feat : features_) {
381  deffacts += " (ff-feature " + feat.first + ")";
382  if (immediate_assert) {
383  // assert so it is immediately available
384  clips->assert_fact_f("(ff-feature %s)", feat.first.c_str());
385  }
386  }
387  deffacts += ")";
388 
389  if (! clips->build(deffacts)) {
390  logger_->log_warn("ClipsEnvManager", "Failed to build deffacts ff-features-available");
391  }
392 }
393 
394 
395 /** Add a feature by name.
396  * @param features CLIPS feature maintainers to add
397  */
398 void
399 CLIPSEnvManager::add_features(const std::list<CLIPSFeature *> &features)
400 {
401  for (auto feat : features) {
402  const std::string &feature_name = feat->clips_feature_name;
403 
404  if (features_.find(feature_name) != features_.end()) {
405  throw Exception("Feature '%s' has already been registered", feature_name.c_str());
406  }
407 
408  logger_->log_info("ClipsEnvManager", "Adding feature %s", feature_name.c_str());
409 
410  features_[feature_name] = feat;
411 
412  // assert fact to indicate feature availability to environments
413  for (auto env : envs_) {
414  env.second.env.lock();
415  assert_features(env.second.env, false);
416  // assert so it is immediately available
417  env.second.env->assert_fact_f("(ff-feature %s)", feature_name.c_str());
418  env.second.env.unlock();
419  }
420  }
421 }
422 
423 
424 /** Assert that a feature can be removed.
425  * The feature will not actually be removed, it will just be checked if this
426  * would work without problem.
427  * @param features list of features to query for removal
428  * @exception Exception thrown with a descriptive message if the feature
429  * cannot be removed because it is still in use
430  */
431 void
432 CLIPSEnvManager::assert_can_remove_features(const std::list<CLIPSFeature *> &features)
433 {
434  for (auto feat : features) {
435  const std::string &feature_name = feat->clips_feature_name;
436 
437  for (auto env : envs_) {
438  if (std::binary_search(env.second.req_feat.begin(), env.second.req_feat.end(), feature_name)) {
439  throw Exception("Cannot remove feature %s as environment %s depends on it",
440  feature_name.c_str(), env.first.c_str());
441  }
442  }
443  }
444 }
445 
446 /** Remove a feature by name.
447  * @param features list of features to remove
448  * @exception Exception thrown with a descriptive message if the feature
449  * cannot be removed because it is still in use
450  */
451 void
452 CLIPSEnvManager::remove_features(const std::list<CLIPSFeature *> &features)
453 {
454  // On plugin unload this would fail because destruction
455  // of threads is forced.
456  //assert_can_remove_features(features);
457  for (auto feat : features) {
458  const std::string &feature_name = feat->clips_feature_name;
459 
460  if (features_.find(feature_name) != features_.end()) {
461  features_.erase(feature_name);
462  }
463  }
464 }
465 
466 
467 void
468 CLIPSEnvManager::guarded_load(const std::string &env_name, const std::string &filename)
469 {
470  if (envs_.find(env_name) == envs_.end()) {
471  throw Exception("guarded_load: env %s has not been registered", env_name.c_str());
472  }
473 
474  LockPtr<CLIPS::Environment> &clips = envs_[env_name].env;
475 
476  int load_rv = 0;
477  if ((load_rv = clips->load(filename)) != 1) {
478  if (load_rv == 0) {
479  destroy_env(env_name);
480  throw Exception("%s: cannot find %s", env_name.c_str(), filename.c_str());
481  } else {
482  destroy_env(env_name);
483  throw Exception("%s: CLIPS code error in %s",
484  env_name.c_str(), filename.c_str());
485  }
486  }
487 }
488 
489 
490 } // end namespace fawkes
void remove_features(const std::list< CLIPSFeature *> &features)
Remove a feature by name.
virtual ~CLIPSEnvManager()
Destructor.
Fawkes library namespace.
CLIPSEnvManager(Logger *logger, Clock *clock, std::string &clips_dir)
Constructor.
This is supposed to be the central clock in Fawkes.
Definition: clock.h:34
A class for handling time.
Definition: time.h:91
void destroy_env(const std::string &env_name)
Destroy the named environment.
void add_features(const std::list< CLIPSFeature *> &features)
Add a feature by name.
void assert_can_remove_features(const std::list< CLIPSFeature *> &features)
Assert that a feature can be removed.
Base class for exceptions in Fawkes.
Definition: exception.h:36
LockPtr< CLIPS::Environment > create_env(const std::string &env_name, const std::string &log_component_name)
Create a new environment.
long get_sec() const
Get seconds.
Definition: time.h:110
long get_usec() const
Get microseconds.
Definition: time.h:112
std::map< std::string, LockPtr< CLIPS::Environment > > environments() const
Get map of environments.
Interface for logging.
Definition: logger.h:34