cirrocumulus 0.6.3 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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