wonkavision 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +0 -0
- data/Rakefile +48 -0
- data/lib/wonkavision.rb +68 -0
- data/lib/wonkavision/acts_as_oompa_loompa.rb +22 -0
- data/lib/wonkavision/event.rb +33 -0
- data/lib/wonkavision/event_binding.rb +21 -0
- data/lib/wonkavision/event_coordinator.rb +75 -0
- data/lib/wonkavision/event_handler.rb +14 -0
- data/lib/wonkavision/event_namespace.rb +72 -0
- data/lib/wonkavision/event_path_segment.rb +37 -0
- data/lib/wonkavision/message_mapper.rb +30 -0
- data/lib/wonkavision/message_mapper/indifferent_access.rb +26 -0
- data/lib/wonkavision/message_mapper/map.rb +126 -0
- data/lib/wonkavision/persistence/mongo_mapper_adapter.rb +33 -0
- data/lib/wonkavision/plugins.rb +30 -0
- data/lib/wonkavision/plugins/business_activity.rb +79 -0
- data/lib/wonkavision/plugins/event_handling.rb +78 -0
- data/lib/wonkavision/plugins/timeline.rb +80 -0
- data/lib/wonkavision/support.rb +0 -0
- data/lib/wonkavision/version.rb +3 -0
- data/test/business_activity_test.rb +24 -0
- data/test/config/database.yml +15 -0
- data/test/event_coordinator_test.rb +72 -0
- data/test/event_handler_test.rb +47 -0
- data/test/event_namespace_test.rb +109 -0
- data/test/event_path_segment_test.rb +56 -0
- data/test/event_test.rb +54 -0
- data/test/log/test.log +7853 -0
- data/test/map_test.rb +187 -0
- data/test/message_mapper_test.rb +21 -0
- data/test/test_activity_models.rb +56 -0
- data/test/test_helper.rb +63 -0
- data/test/timeline_test.rb +61 -0
- data/test/wonkavision_test.rb +10 -0
- data/wonkavision.gemspec +94 -0
- metadata +145 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2010 Nathan Stults
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
require File.dirname(__FILE__) + "/lib/wonkavision/version"
|
9
|
+
|
10
|
+
Jeweler::Tasks.new do |s|
|
11
|
+
s.name = "wonkavision"
|
12
|
+
s.version = Wonkavision::VERSION
|
13
|
+
s.summary = "Simple Business Activity Monitoring"
|
14
|
+
s.description = "Wonkavision is a small gem that allows you to publish"
|
15
|
+
s.email = "hereiam@sonic.net"
|
16
|
+
s.homepage = "http://github.com/PlasticLizard/wonkavision"
|
17
|
+
s.authors = ["Nathan Stults"]
|
18
|
+
s.has_rdoc = false #=>Should be true, someday
|
19
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
|
20
|
+
s.files = FileList["[A-Z]*", "{bin,lib,test}/**/*"]
|
21
|
+
|
22
|
+
s.add_dependency('activesupport', '>= 2.3')
|
23
|
+
|
24
|
+
s.add_development_dependency('shoulda', '2.10.3')
|
25
|
+
end
|
26
|
+
|
27
|
+
Jeweler::GemcutterTasks.new
|
28
|
+
|
29
|
+
rescue LoadError => ex
|
30
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::TestTask.new do |t|
|
35
|
+
t.libs << 'libs' << 'test'
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
37
|
+
t.verbose = false
|
38
|
+
end
|
39
|
+
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = 'test'
|
43
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :test
|
data/lib/wonkavision.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "active_support"
|
3
|
+
require "active_support/hash_with_indifferent_access" unless defined?(HashWithIndifferentAccess)
|
4
|
+
|
5
|
+
dir = File.dirname(__FILE__)
|
6
|
+
["support",
|
7
|
+
"plugins",
|
8
|
+
"event_path_segment",
|
9
|
+
"event",
|
10
|
+
"event_namespace",
|
11
|
+
"event_coordinator",
|
12
|
+
"event_binding",
|
13
|
+
"event_handler",
|
14
|
+
"message_mapper/indifferent_access",
|
15
|
+
"message_mapper/map",
|
16
|
+
"message_mapper",
|
17
|
+
"plugins/event_handling",
|
18
|
+
"plugins/business_activity",
|
19
|
+
"plugins/timeline",
|
20
|
+
"acts_as_oompa_loompa",
|
21
|
+
"persistence/mongo_mapper_adapter"].each {|lib|require File.join(dir,'wonkavision',lib)}
|
22
|
+
|
23
|
+
#require File.join(dir,"cubicle","mongo_mapper","aggregate_plugin") if defined?(MongoMapper::Document)
|
24
|
+
|
25
|
+
module Wonkavision
|
26
|
+
|
27
|
+
# def self.register_cubicle_directory(directory_path, recursive=true)
|
28
|
+
# searcher = "#{recursive ? "*" : "**/*"}.rb"
|
29
|
+
# Dir[File.join(directory_path,searcher)].each {|cubicle| require cubicle}
|
30
|
+
# end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
attr_accessor :event_path_separator
|
35
|
+
|
36
|
+
def event_coordinator
|
37
|
+
@event_coordinator ||= Wonkavision::EventCoordinator.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def event_path_separator
|
41
|
+
@event_path_separator ||= '/'
|
42
|
+
end
|
43
|
+
|
44
|
+
def namespace_wildcard_character
|
45
|
+
@namespace_wildcard_character = "*"
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_absolute_path(path)
|
49
|
+
path.to_s[0..0] == event_path_separator
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalize_event_path(event_path)
|
53
|
+
event_path.to_s.split(event_path_separator).map{|s|s.underscore}.join(event_path_separator)
|
54
|
+
end
|
55
|
+
|
56
|
+
def join (*args)
|
57
|
+
args.map!{|segment|normalize_event_path(segment)}
|
58
|
+
args.reject{|segment|segment.blank?}.join(event_path_separator)
|
59
|
+
end
|
60
|
+
|
61
|
+
def split(event_path)
|
62
|
+
event_path.split(event_path_separator)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
module ActsAsOompaLoompa
|
3
|
+
|
4
|
+
def acts_as_event_handler
|
5
|
+
include Wonkavision::EventHandler
|
6
|
+
end
|
7
|
+
|
8
|
+
def acts_as_business_activity(opts={})
|
9
|
+
acts_as_event_handler
|
10
|
+
use Wonkavision::Plugins::BusinessActivity, opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def acts_as_timeline(opts={})
|
14
|
+
acts_as_business_activity(opts)
|
15
|
+
use Wonkavision::Plugins::Timeline, opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def acts_like_a_child(opts={})
|
19
|
+
raise "I don't want to include any plugins!" if (1 + rand(10)) % 3 == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class Event < Wonkavision::EventPathSegment
|
4
|
+
|
5
|
+
def initialize(name, namespace, opts={})
|
6
|
+
super name,namespace,opts
|
7
|
+
@source_events = []
|
8
|
+
source_events(*opts.delete(:source_events)) if opts.keys.include?(:source_events)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches(event_path)
|
12
|
+
event_path == path || @source_events.detect{|evt|evt.matches(event_path)} != nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify_subscribers(event_data,event_path)
|
16
|
+
super(event_data,self.path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_events(*args)
|
20
|
+
return @source_events if args.blank?
|
21
|
+
@source_events = @source_events.concat(args.map do |source|
|
22
|
+
source_ns = namespace
|
23
|
+
if (source=~/^\/.*/)
|
24
|
+
source_ns = root_namespace
|
25
|
+
source = source[1..-1]
|
26
|
+
end
|
27
|
+
source_ns.find_or_create(source)
|
28
|
+
end)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class EventBinding
|
4
|
+
attr_reader :name, :events, :options
|
5
|
+
|
6
|
+
def initialize(name,handler,*args)
|
7
|
+
@options = args.extract_options!
|
8
|
+
@handler = handler
|
9
|
+
@name = name
|
10
|
+
@events = []
|
11
|
+
args = [@name] if args.blank? || args.flatten.blank?
|
12
|
+
@events = args.flatten.map{|evt_name|@handler.event_path(evt_name)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe_to_events(&block)
|
16
|
+
@events.each do |event|
|
17
|
+
Wonkavision.event_coordinator.subscribe(event,&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
class EventCoordinator
|
3
|
+
|
4
|
+
attr_reader :root_namespace
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@root_namespace = Wonkavision::EventNamespace.new
|
8
|
+
@lock = Mutex.new
|
9
|
+
#@event_cache = {}
|
10
|
+
@incoming_event_filters = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_receive_event(&block)
|
14
|
+
@incoming_event_filters << block
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear_filters
|
18
|
+
@incoming_event_filters = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def configure(&block)
|
22
|
+
self.instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def map ()
|
26
|
+
yield root_namespace if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
def subscribe(event_path,final_segment_type=nil,&block)
|
30
|
+
event_path, final_segment_type = *detect_final_segment(event_path) unless final_segment_type
|
31
|
+
segment = (event_path.blank? ? root_namespace : root_namespace.find_or_create(event_path,final_segment_type))
|
32
|
+
segment.subscribe(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive_event(event_path, event_data)
|
36
|
+
@lock.synchronize do
|
37
|
+
#If process_incoming_event returns nil or false, it means a filter chose to abort
|
38
|
+
#the event processing, in which case we'll break for lunch.
|
39
|
+
return unless event_data = process_incoming_event(event_path,event_data)
|
40
|
+
|
41
|
+
event_path = Wonkavision.normalize_event_path(event_path)
|
42
|
+
targets = root_namespace.find_matching_events(event_path)
|
43
|
+
#If the event wasn't matched, maybe someone is subscribing to '/*' ?
|
44
|
+
targets = [root_namespace] if targets.blank?
|
45
|
+
targets.each{|target|target.notify_subscribers(event_data,event_path)}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def process_incoming_event(event_path,event_data)
|
51
|
+
return nil unless event_data
|
52
|
+
@incoming_event_filters.each do |filter_block|
|
53
|
+
if (filter_block.arity < 1)
|
54
|
+
event_data = event_data.instance_eval(&filter_block)
|
55
|
+
elsif filter_block.arity == 1
|
56
|
+
event_data = filter_block.call(event_data)
|
57
|
+
else
|
58
|
+
event_data = filter_block.call(event_data,event_path)
|
59
|
+
end
|
60
|
+
return nil unless event_data
|
61
|
+
end
|
62
|
+
event_data
|
63
|
+
end
|
64
|
+
|
65
|
+
def detect_final_segment(event_path)
|
66
|
+
parts = Wonkavision.split(event_path)
|
67
|
+
if parts[-1] == Wonkavision.namespace_wildcard_character
|
68
|
+
[Wonkavision.join(parts.slice(0..-2)),:namespace]
|
69
|
+
else
|
70
|
+
[event_path,:event]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class EventNamespace < EventPathSegment
|
4
|
+
|
5
|
+
attr_reader :children
|
6
|
+
|
7
|
+
def initialize(name=nil,namespace = nil,opts={})
|
8
|
+
super name, namespace,opts
|
9
|
+
@children=HashWithIndifferentAccess.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_or_create (path, final_segment_type = :event)
|
13
|
+
if path.is_a?(Array)
|
14
|
+
child_name = path.shift
|
15
|
+
segment_type = path.blank? ? final_segment_type : :namespace
|
16
|
+
child = @children[child_name] ||= self.send(segment_type,child_name)
|
17
|
+
return child if path.blank?
|
18
|
+
raise "Events cannot have children. The path you requested is not valid" if child.is_a?(Wonkavision::Event)
|
19
|
+
child.find_or_create(path,final_segment_type)
|
20
|
+
else
|
21
|
+
path = Wonkavision.normalize_event_path(path)
|
22
|
+
source_ns = self
|
23
|
+
if (Wonkavision.is_absolute_path(path)) #indicates an absolute path, because it begins with a '/'
|
24
|
+
source_ns = root_namespace
|
25
|
+
path = path[1..-1]
|
26
|
+
end
|
27
|
+
source_ns.find_or_create(path.split(Wonkavision.event_path_separator), final_segment_type)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_matching_events (event_path)
|
32
|
+
events = []
|
33
|
+
@children.each_value do |child|
|
34
|
+
if (child.is_a?(Wonkavision::Event))
|
35
|
+
events << child if child.matches(event_path)
|
36
|
+
elsif (child.is_a?(Wonkavision::EventNamespace))
|
37
|
+
events.concat(child.find_matching_events(event_path))
|
38
|
+
else
|
39
|
+
raise "An unexpected child type was encountered in find_matching_events #{child.class}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
#If no child was found, and the event matches this namespace, we should add ourselves to the list.
|
43
|
+
#This is not necessary if any child event was located, because in that case event notifications
|
44
|
+
#would bubble up to us anyway. If no event was found, there's nobody to blow the bubble but us.
|
45
|
+
events << self if events.blank? && matches_event(event_path)
|
46
|
+
events
|
47
|
+
end
|
48
|
+
|
49
|
+
def matches_event(event_path)
|
50
|
+
Wonkavision.split(path) == Wonkavision.split(event_path).slice(0..-2)
|
51
|
+
end
|
52
|
+
|
53
|
+
#dsl
|
54
|
+
def namespace(*args)
|
55
|
+
return super if args.blank?
|
56
|
+
name, opts = args.shift, (args.shift || {})
|
57
|
+
ns = Wonkavision::EventNamespace.new(name,self,opts)
|
58
|
+
yield ns if block_given?
|
59
|
+
@children[ns.name] = ns
|
60
|
+
ns
|
61
|
+
end
|
62
|
+
|
63
|
+
def event(*args)
|
64
|
+
name, opts = args.shift, args.extract_options!
|
65
|
+
opts[:source_events] = (opts[:source_events] || []).concat(args) unless args.blank?
|
66
|
+
evt = Wonkavision::Event.new(name,self,opts)
|
67
|
+
yield evt if block_given?
|
68
|
+
@children[evt.name] = evt
|
69
|
+
evt
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
class EventPathSegment
|
3
|
+
attr_reader :name, :namespace, :options, :subscribers
|
4
|
+
|
5
|
+
def initialize(name = nil, namespace = nil, opts={})
|
6
|
+
@name = name
|
7
|
+
@namespace = namespace
|
8
|
+
@options = opts
|
9
|
+
@subscribers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def path
|
13
|
+
Wonkavision.join(namespace.blank? ? nil : namespace.path,name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def subscribe(&block)
|
17
|
+
@subscribers << block
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify_subscribers(event_data, event_path=self.path)
|
22
|
+
@subscribers.each do |sub|
|
23
|
+
sub.call(event_data,event_path)
|
24
|
+
end
|
25
|
+
namespace.notify_subscribers(event_data,event_path) if namespace
|
26
|
+
end
|
27
|
+
|
28
|
+
def root_namespace
|
29
|
+
cur_namespace = self
|
30
|
+
while (cur_namespace.namespace)
|
31
|
+
cur_namespace=cur_namespace.namespace
|
32
|
+
end
|
33
|
+
cur_namespace
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
module MessageMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def maps
|
7
|
+
@maps ||={}
|
8
|
+
end
|
9
|
+
|
10
|
+
def register(map_name,&block)
|
11
|
+
MessageMapper.maps[map_name] = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(map,data_source)
|
15
|
+
map_block = map.kind_of?(Proc) ? map : MessageMapper.maps[map]
|
16
|
+
|
17
|
+
raise "#{map} not found" unless map_block
|
18
|
+
message = MessageMapper::Map.new(data_source)
|
19
|
+
message.instance_eval(&map_block)
|
20
|
+
message
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_map_directory(directory_path, recursive=true)
|
24
|
+
searcher = "#{recursive ? "*" : "**/*"}.rb"
|
25
|
+
Dir[File.join(directory_path,searcher)].each {|map| require map}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|