wonkavision 0.5.0

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 (38) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.rdoc +0 -0
  4. data/Rakefile +48 -0
  5. data/lib/wonkavision.rb +68 -0
  6. data/lib/wonkavision/acts_as_oompa_loompa.rb +22 -0
  7. data/lib/wonkavision/event.rb +33 -0
  8. data/lib/wonkavision/event_binding.rb +21 -0
  9. data/lib/wonkavision/event_coordinator.rb +75 -0
  10. data/lib/wonkavision/event_handler.rb +14 -0
  11. data/lib/wonkavision/event_namespace.rb +72 -0
  12. data/lib/wonkavision/event_path_segment.rb +37 -0
  13. data/lib/wonkavision/message_mapper.rb +30 -0
  14. data/lib/wonkavision/message_mapper/indifferent_access.rb +26 -0
  15. data/lib/wonkavision/message_mapper/map.rb +126 -0
  16. data/lib/wonkavision/persistence/mongo_mapper_adapter.rb +33 -0
  17. data/lib/wonkavision/plugins.rb +30 -0
  18. data/lib/wonkavision/plugins/business_activity.rb +79 -0
  19. data/lib/wonkavision/plugins/event_handling.rb +78 -0
  20. data/lib/wonkavision/plugins/timeline.rb +80 -0
  21. data/lib/wonkavision/support.rb +0 -0
  22. data/lib/wonkavision/version.rb +3 -0
  23. data/test/business_activity_test.rb +24 -0
  24. data/test/config/database.yml +15 -0
  25. data/test/event_coordinator_test.rb +72 -0
  26. data/test/event_handler_test.rb +47 -0
  27. data/test/event_namespace_test.rb +109 -0
  28. data/test/event_path_segment_test.rb +56 -0
  29. data/test/event_test.rb +54 -0
  30. data/test/log/test.log +7853 -0
  31. data/test/map_test.rb +187 -0
  32. data/test/message_mapper_test.rb +21 -0
  33. data/test/test_activity_models.rb +56 -0
  34. data/test/test_helper.rb +63 -0
  35. data/test/timeline_test.rb +61 -0
  36. data/test/wonkavision_test.rb +10 -0
  37. data/wonkavision.gemspec +94 -0
  38. metadata +145 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+
2
+ == 0.5.0
3
+ * Initial release
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
@@ -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,14 @@
1
+ module Wonkavision
2
+
3
+ module EventHandler
4
+
5
+ def self.included(handler)
6
+ handler.class_eval do
7
+ extend Plugins
8
+ use Plugins::EventHandling
9
+ end
10
+
11
+ super
12
+ end
13
+ end
14
+ 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