wonkavision 0.5.0

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