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