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