cirrocumulus 0.6.3 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/README.rdoc +1 -22
  2. data/lib/cirrocumulus.rb +8 -2
  3. data/lib/cirrocumulus/{message.rb → agents/message.rb} +0 -0
  4. data/lib/cirrocumulus/channels.rb +129 -0
  5. data/lib/cirrocumulus/channels/jabber.rb +168 -0
  6. data/lib/cirrocumulus/environment.rb +46 -0
  7. data/lib/cirrocumulus/facts.rb +135 -0
  8. data/lib/cirrocumulus/identifier.rb +84 -0
  9. data/lib/cirrocumulus/ontology.rb +333 -49
  10. data/lib/cirrocumulus/pattern_matching.rb +175 -0
  11. data/lib/cirrocumulus/remote_console.rb +29 -0
  12. data/lib/cirrocumulus/rule_queue.rb +69 -0
  13. data/lib/cirrocumulus/rules/engine.rb +7 -5
  14. data/lib/cirrocumulus/rules/fact.rb +22 -0
  15. data/lib/cirrocumulus/rules/pattern_matcher.rb +19 -0
  16. data/lib/cirrocumulus/rules/run_queue.rb +1 -0
  17. data/lib/cirrocumulus/saga.rb +44 -25
  18. data/lib/console.rb +96 -0
  19. metadata +79 -72
  20. data/.document +0 -5
  21. data/.idea/.name +0 -1
  22. data/.idea/.rakeTasks +0 -7
  23. data/.idea/cirrocumulus.iml +0 -87
  24. data/.idea/encodings.xml +0 -5
  25. data/.idea/misc.xml +0 -5
  26. data/.idea/modules.xml +0 -9
  27. data/.idea/scopes/scope_settings.xml +0 -5
  28. data/.idea/vcs.xml +0 -7
  29. data/.idea/workspace.xml +0 -614
  30. data/Gemfile +0 -18
  31. data/Gemfile.lock +0 -43
  32. data/Rakefile +0 -37
  33. data/VERSION +0 -1
  34. data/cirrocumulus.gemspec +0 -106
  35. data/lib/.gitignore +0 -5
  36. data/lib/cirrocumulus/agent_wrapper.rb +0 -67
  37. data/lib/cirrocumulus/jabber_bus.rb +0 -140
  38. data/lib/cirrocumulus/rule_engine.rb +0 -2
  39. data/lib/cirrocumulus/rule_server.rb +0 -49
  40. data/test/Gemfile +0 -3
  41. data/test/Gemfile.lock +0 -27
  42. data/test/helper.rb +0 -18
  43. data/test/test.rb +0 -85
  44. data/test/test2.rb +0 -30
  45. data/test/test_cirrocumulus.rb +0 -7
@@ -0,0 +1,175 @@
1
+ class MatchResult
2
+ def initialize(rule)
3
+ @rule = rule
4
+ @matched_facts = []
5
+ @parameters = nil
6
+ end
7
+
8
+ attr_reader :rule
9
+ attr_reader :matched_facts
10
+ attr_accessor :parameters
11
+
12
+ def ==(other)
13
+ rule == other.rule && matched_facts == other.matched_facts
14
+ end
15
+ end
16
+
17
+ class PatternMatcher
18
+ def initialize(facts)
19
+ @facts = facts
20
+ end
21
+
22
+ def match(pattern)
23
+ res = []
24
+ find_matches_for_condition(pattern).each do |fact|
25
+ res << bind_parameters(pattern, fact.data, {})
26
+ end
27
+
28
+ res
29
+ end
30
+
31
+ def matches?(rule)
32
+ trace "Processing rule '#{rule.name}' (#{rule.conditions.size} condition(s)):"
33
+
34
+ pattern_candidates = []
35
+ rule.conditions.each do |pattern|
36
+ pattern_candidates << find_matches_for_condition(pattern)
37
+ end
38
+
39
+ return nil if !pattern_candidates.all? {|c| c.size > 0}
40
+
41
+ intersect_matches_for_each_condition(rule, pattern_candidates)
42
+ end
43
+
44
+ def find_matches_for_condition(pattern)
45
+ trace "=> attempting to match pattern #{pattern.inspect}"
46
+ fact_matches = true
47
+ candidates = []
48
+
49
+ @facts.each do |fact|
50
+ next if fact.data.size != pattern.size
51
+ fact_matches = true
52
+
53
+ pattern.each_with_index do |el,i|
54
+ if el.is_a?(Symbol) && el.to_s.upcase == el.to_s # parameter
55
+ else
56
+ fact_matches = false if el != fact.data[i]
57
+ end
58
+ end
59
+
60
+ candidates << fact if fact_matches
61
+ end
62
+
63
+ trace "=> candidates: #{candidates.size}" if candidates.size > 0
64
+ candidates
65
+ end
66
+
67
+ def intersect_matches_for_each_condition(rule, candidates)
68
+ result = []
69
+ attempt = []
70
+ while (attempt = generate_combination(rule, candidates, attempt)) != [] do
71
+ bindings = test_condition_parameters_combination(rule, candidates, attempt)
72
+ if bindings
73
+ match_data = MatchResult.new(rule)
74
+ attempt.each_with_index {|a,i| match_data.matched_facts << candidates[i][a]}
75
+ match_data.parameters = bindings
76
+ result << match_data
77
+ end
78
+ end
79
+
80
+ result
81
+ end
82
+
83
+ def test_condition_parameters_combination(rule, candidates, attempt)
84
+ facts = []
85
+ attempt.each_with_index {|a,i| facts << candidates[i][a].data}
86
+
87
+ binded_params = {}
88
+ pattern_params = {}
89
+ facts.each_with_index do |fact,i|
90
+ pattern_params = bind_parameters(rule.conditions[i], fact, binded_params)
91
+ if pattern_params.nil? # failure, parameters mismatch
92
+ return nil
93
+ else
94
+ binded_params.merge!(pattern_params)
95
+ end
96
+ end
97
+
98
+ binded_params
99
+ end
100
+
101
+ def bind_parameters(pattern, fact, current_bindings)
102
+ result = {}
103
+
104
+ pattern.each_with_index do |p,i|
105
+ if p.is_a?(Symbol) && p.to_s.upcase == p.to_s
106
+ return nil if current_bindings.has_key?(p) && current_bindings[p] != fact[i]
107
+ result[p] = fact[i]
108
+ end
109
+ end
110
+
111
+ result
112
+ end
113
+
114
+ def generate_combination(rule, candidates, attempt)
115
+ next_attempt = []
116
+
117
+ if attempt == []
118
+ rule.conditions.each {|pattern| next_attempt << 0}
119
+ else
120
+ next_attempt = increment_attempt(attempt, rule.conditions.size - 1, candidates.map {|c| c.size})
121
+ end
122
+
123
+ next_attempt
124
+ end
125
+
126
+ def increment_attempt(attempt, idx, limits)
127
+ return [] if idx < 0
128
+
129
+ if attempt[idx] < limits[idx] - 1
130
+ attempt[idx] += 1
131
+ else
132
+ i = idx
133
+ while i < limits.size do
134
+ attempt[i] = 0
135
+ i += 1
136
+ end
137
+
138
+ return increment_attempt(attempt, idx-1, limits)
139
+ end
140
+
141
+ attempt
142
+ end
143
+
144
+ def pattern_matches?(fact, pattern, current_params = {})
145
+ return nil if fact.size != pattern.size
146
+
147
+ binded_params = {}
148
+
149
+ pattern.each_with_index do |el,i|
150
+ if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
151
+ if current_params && current_params.has_key?(el)
152
+ current_value = current_params[el]
153
+ return nil if fact[i] != current_value
154
+ else
155
+ binded_params[el] = fact[i]
156
+ end
157
+ else
158
+ return nil if el != fact[i]
159
+ end
160
+ end
161
+
162
+ binded_params
163
+ end
164
+
165
+ private
166
+
167
+ def trace(msg)
168
+ puts "[TRACE] %s" % msg if false
169
+ end
170
+
171
+ def debug(msg)
172
+ puts "[DBG] %s" % msg if false
173
+ end
174
+
175
+ end
@@ -0,0 +1,29 @@
1
+ require 'drb'
2
+ require 'sexpistol'
3
+ require_relative 'identifier'
4
+
5
+ class RemoteConsole
6
+ def initialize
7
+
8
+ end
9
+
10
+ def list_inproc_agents
11
+ Ontology.list_ontology_instances()
12
+ end
13
+
14
+ def assert(instance, data)
15
+ Ontology.assert(LocalIdentifier.new(instance), Sexpistol.new.parse_string(data)[0])
16
+ end
17
+
18
+ def retract(instance, data)
19
+ Ontology.retract(LocalIdentifier.new(instance), Sexpistol.new.parse_string(data)[0])
20
+ end
21
+
22
+ def dump_kb(instance)
23
+ Ontology.dump_kb(LocalIdentifier.new(instance))
24
+ end
25
+
26
+ def dump_sagas(instance)
27
+ Ontology.dump_sagas(LocalIdentifier.new(instance))
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ require 'thread'
2
+
3
+ class RuleQueue
4
+ class QueueEntry
5
+ attr_reader :run_data
6
+ attr_reader :rule
7
+ attr_reader :params
8
+ attr_reader :run_at
9
+ attr_accessor :state
10
+
11
+ def initialize(run_data, run_at = nil)
12
+ @run_data = run_data
13
+ @rule = run_data.rule
14
+ @params = run_data.parameters
15
+ @run_at = run_at
16
+ @state = :queued
17
+ end
18
+ end
19
+
20
+ def initialize(ontology_instance)
21
+ @mutex = Mutex.new
22
+ @queue = []
23
+ @ontology_instance = ontology_instance
24
+ end
25
+
26
+ def push(entry)
27
+ @mutex.synchronize do
28
+ @queue.push(QueueEntry.new(entry)) unless @queue.find {|e| e.state != :finished && e.run_data == entry}
29
+ end
30
+ end
31
+
32
+ def pop
33
+ @mutex.synchronize do
34
+ @queue.empty? ? nil : @queue.shift
35
+ end
36
+ end
37
+
38
+ def size
39
+ @mutex.synchronize do
40
+ @queue.size
41
+ end
42
+ end
43
+
44
+ def run_queued_rules
45
+ while (entry = pop) != nil do
46
+ next if entry.state == :finished
47
+ if entry.run_at && (entry.run_at < Time.now)
48
+ push(entry)
49
+ next
50
+ end
51
+
52
+ if entry.run_data.matched_facts.all? {|fact| !fact.is_deleted}
53
+ begin
54
+ debug "Executing #{entry.rule.name}#{entry.params.inspect}"
55
+ entry.rule.code.call(@ontology_instance, entry.params)
56
+ entry.state
57
+ rescue Exception => e
58
+ puts "[WARN] Exception while executing rule: %s\n%s" % [e.to_s, e.backtrace.to_s]
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def debug(msg)
67
+ puts msg
68
+ end
69
+ end
@@ -182,10 +182,12 @@ module RuleEngine
182
182
  end
183
183
 
184
184
  def tick()
185
- to_retract = []
186
- @facts.each {|fact| to_retract << fact if fact.timed_out? }
187
- to_retract.each {|fact| retract(fact.data, true) }
188
- process() unless to_retract.empty?
185
+ @mutex.synchronize do
186
+ to_retract = []
187
+ @facts.each {|fact| to_retract << fact if fact.timed_out? }
188
+ to_retract.each {|fact| retract_nonblocking(fact.data, true) }
189
+ process() unless to_retract.empty?
190
+ end
189
191
  rescue Exception => ex
190
192
  p ex
191
193
  end
@@ -386,7 +388,7 @@ module RuleEngine
386
388
  def process()
387
389
  self.class.current_ruleset.each do |rule|
388
390
  binded_params = matches?(rule)
389
- next if binded_params.blank?
391
+ next if binded_params.nil? || binded_params.empty?
390
392
 
391
393
  binded_params.each {|params| execute_rule(params)}
392
394
  end
@@ -0,0 +1,22 @@
1
+ module Cirrocumulus
2
+ module Ruler
3
+ # Fact.
4
+ # Piece of information, collected and operated in Cirrocumulus.
5
+ # We also remember the time, when this fact was created (observed). Optionally can have expiration time
6
+ class Fact
7
+ attr_reader :data
8
+ attr_accessor :is_deleted
9
+
10
+ def initialize(data, time, options)
11
+ @data = data
12
+ @time = time
13
+ @options = options
14
+ end
15
+
16
+ def expired?
17
+ return false if @options[:expires] == nil
18
+ @time + @options[:expires] < Time.now
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Cirrocumulus
2
+ module Ruler
3
+ # The Matcher.
4
+ # Matches and compares facts with each other. Searches and bind variables.
5
+ class PatternMatcher
6
+ def match(pattern, fact)
7
+ return false if fact.data.size != pattern.size
8
+ fact_matches = true
9
+
10
+ pattern.each_with_index do |el,i|
11
+ next if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
12
+ fact_matches = false if el != fact.data[i]
13
+ end
14
+
15
+ fact_matches
16
+ end
17
+ end
18
+ end
19
+ end
@@ -64,6 +64,7 @@ module RuleEngine
64
64
  entry.state = :running
65
65
  end
66
66
 
67
+ idx += 1
67
68
  count -= 1
68
69
  end
69
70
  end
@@ -1,55 +1,74 @@
1
+ #
2
+ # Saga. Implements long-running workflows
3
+ #
1
4
  class Saga
2
- attr_reader :id
3
- attr_reader :context
4
- attr_reader :finished
5
+ class << self
6
+ @@saga_names = {}
7
+
8
+ def saga(saga_name = nil)
9
+ return @@saga_names[name] if saga_name.nil?
10
+ @@saga_names[name] = saga_name
11
+ end
12
+ end
5
13
 
6
- DEFAULT_TIMEOUT = 15
7
- LONG_TIMEOUT = 60
8
-
9
14
  STATE_ERROR = -1
10
15
  STATE_START = 0
11
16
  STATE_FINISHED = 255
12
17
 
18
+ attr_reader :id
19
+
13
20
  def initialize(id, ontology)
14
21
  @id = id
15
22
  @ontology = ontology
16
- @finished = false
17
- @timeout_at = -1
18
23
  @state = STATE_START
24
+ @started_at = Time.now
19
25
  end
20
26
 
21
27
  def is_finished?
22
- @finished || @state == STATE_ERROR
28
+ @state == STATE_ERROR || @state == STATE_FINISHED
23
29
  end
24
30
 
25
- def timed_out?(time)
26
- @timeout_at > 0 && @timeout_at <= time
31
+ def handle_reply(sender, contents, options = {}); end
32
+
33
+ def dump_parameters
34
+ ""
35
+ end
36
+
37
+ def to_s
38
+ "%s type=%s, started at %s, state=%d, params: %s" % [@id, @@saga_names[self.class.name], @started_at, @state, dump_parameters]
27
39
  end
28
40
 
29
41
  protected
30
42
 
31
- def clear_timeout()
32
- @timeout_at = -1
43
+ #
44
+ # Inter-agent communications with context of this saga.
45
+ #
46
+ def inform(agent, proposition)
47
+ @ontology.inform agent, proposition, :conversation_id => self.id
33
48
  end
34
49
 
35
- def set_timeout(secs)
36
- Log4r::Logger['agent'].debug "[#{id}] waiting for #{secs} second(s)" if secs > 1
37
- @timeout_at = Time.now.to_i + secs
50
+ def request(agent, action)
51
+ @ontology.request agent, action, :conversation_id => self.id
38
52
  end
39
-
40
- def change_state(new_state)
41
- Log4r::Logger['agent'].debug "[#{id}] switching state from #{@state} to #{new_state}"
42
- @state = new_state
53
+
54
+ def query(agent, expression)
55
+ @ontology.query agent, expression, :reply_with => self.id
56
+ end
57
+
58
+ def query_if(agent, proposition)
59
+ @ontology.query_if agent, proposition, :reply_with => self.id
43
60
  end
44
-
61
+
45
62
  def finish()
46
- clear_timeout()
47
63
  change_state(STATE_FINISHED)
48
- @finished = true
49
- Log4r::Logger['agent'].debug "[#{id}] finished"
50
64
  end
51
-
65
+
52
66
  def error()
53
67
  change_state(STATE_ERROR)
54
68
  end
69
+
70
+ def change_state(new_state)
71
+ @state = new_state
72
+ end
73
+
55
74
  end