ruote-stomp 2.2.0.a

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.
@@ -0,0 +1,156 @@
1
+
2
+ require 'ruote-stomp'
3
+
4
+
5
+ module RuoteStomp
6
+
7
+ #
8
+ # = Stomp Receiver
9
+ #
10
+ # Used in conjunction with the RuoteStomp::Participant, the WorkitemListener
11
+ # subscribes to a specific direct exchange and monitors for
12
+ # incoming workitems. It expects workitems to arrive serialized as
13
+ # JSON.
14
+ #
15
+ # == Configuration
16
+ #
17
+ # Stomp configuration is handled by directly manipulating the values of
18
+ # the +Stomp.settings+ hash, as provided by the Stomp gem. No
19
+ # defaults are set by the listener. The only +option+ parsed by
20
+ # the initializer of the workitem listener is the +queue+ key (Hash
21
+ # expected). If no +queue+ key is set, the listener will subscribe
22
+ # to the +ruote_workitems+ direct exchange for workitems, otherwise it will
23
+ # subscribe to the direct exchange provided.
24
+ #
25
+ # == Usage
26
+ #
27
+ # Register the engine or storage with the listener:
28
+ #
29
+ # RuoteStomp::Receiver.new(engine_or_storage)
30
+ #
31
+ # The workitem listener leverages the asynchronous nature of the stomp gem,
32
+ # so no timers are setup when initialized.
33
+ #
34
+ # == Options
35
+ #
36
+ # :queue and :launchitems
37
+ #
38
+ # See the RuoteStomp::Participant docs for information on sending
39
+ # workitems out to remote participants, and have them send replies
40
+ # to the correct direct exchange specified in the workitem
41
+ # attributes.
42
+ #
43
+ class Receiver < Ruote::Receiver
44
+
45
+ attr_reader :queue
46
+
47
+ # Starts a new Receiver
48
+ #
49
+ # Two arguments for this method.
50
+ #
51
+ # The first one should be a Ruote::Engine, a Ruote::Storage or
52
+ # a Ruote::Worker instance.
53
+ #
54
+ # The second one is a hash for options. There are two known options :
55
+ #
56
+ # :queue for setting the queue on which to listen (defaults to
57
+ # 'ruote_workitems').
58
+ #
59
+ # The :launchitems option :
60
+ #
61
+ # :launchitems => true
62
+ # # the receiver accepts workitems and launchitems
63
+ # :launchitems => false
64
+ # # the receiver only accepts workitems
65
+ # :launchitems => :only
66
+ # # the receiver only accepts launchitems
67
+ #
68
+ def initialize(engine_or_storage, opts={})
69
+
70
+ super(engine_or_storage)
71
+
72
+ @launchitems = opts[:launchitems]
73
+
74
+ @queue =
75
+ opts[:queue] ||
76
+ (@launchitems == :only ? '/queue/ruote_launchitems' : '/queue/ruote_workitems')
77
+
78
+ RuoteStomp.start!
79
+
80
+ if opts[:unsubscribe]
81
+ #MQ.queue(@queue, :durable => true).unsubscribe
82
+ $stomp.unsubscribe(@queue)
83
+ sleep 0.300
84
+ end
85
+
86
+ $stomp.subscribe(@queue, {}) do |message|
87
+ # Process your message here
88
+ # Your submitted data is in msg.body
89
+ if $stomp.closed?
90
+ # do nothing, we're going down
91
+ else
92
+ handle(message)
93
+ end
94
+ end
95
+ #$stomp.join # Wait until listening thread dies
96
+
97
+ # MQ.queue(@queue, :durable => true).subscribe do |message|
98
+ # if AMQP.closing?
99
+ # # do nothing, we're going down
100
+ # else
101
+ # handle(message)
102
+ # end
103
+ # end
104
+ end
105
+
106
+ def stop
107
+
108
+ RuoteStomp.stop!
109
+ end
110
+
111
+ # (feel free to overwrite me)
112
+ #
113
+ def decode_workitem(msg)
114
+
115
+ (Rufus::Json.decode(msg) rescue nil)
116
+ end
117
+
118
+ private
119
+
120
+ def handle(msg)
121
+ item = decode_workitem(msg.body)
122
+ return unless item.is_a?(Hash)
123
+
124
+ not_li = ! item.has_key?('definition')
125
+
126
+ return if @launchitems == :only && not_li
127
+ return unless @launchitems || not_li
128
+
129
+ if not_li
130
+ receive(item) # workitem resumes in its process instance
131
+ else
132
+ launch(item) # new process instance launch
133
+ end
134
+
135
+ rescue => e
136
+ # something went wrong
137
+ # let's simply discard the message
138
+ $stderr.puts('=' * 80)
139
+ $stderr.puts(self.class.name)
140
+ $stderr.puts("couldn't handle incoming message :")
141
+ $stderr.puts('')
142
+ $stderr.puts(msg.inspect)
143
+ $stderr.puts('')
144
+ $stderr.puts(Rufus::Json.pretty_encode(item)) rescue nil
145
+ $stderr.puts('')
146
+ $stderr.puts(e.inspect)
147
+ $stderr.puts(e.backtrace)
148
+ $stderr.puts('=' * 80)
149
+ end
150
+
151
+ def launch(hash)
152
+ super(hash['definition'], hash['fields'] || {}, hash['variables'] || {})
153
+ end
154
+ end
155
+ end
156
+
@@ -0,0 +1,6 @@
1
+
2
+ module RuoteStomp
3
+
4
+ VERSION = '2.2.0.a'
5
+ end
6
+
@@ -0,0 +1,12 @@
1
+
2
+ module RuoteStomp
3
+
4
+ #
5
+ # Got replaced by RuoteStomp::Receiver
6
+ #
7
+ # This class is kept for backward compatibility.
8
+ #
9
+ class WorkitemListener < ::RuoteStomp::Receiver
10
+ end
11
+ end
12
+
@@ -0,0 +1,96 @@
1
+ require 'stomp'
2
+
3
+ require 'ruote-stomp/version'
4
+
5
+
6
+ #
7
+ # Stomp participant and listener pair for ruote.
8
+ #
9
+ # == Documentation
10
+ #
11
+ # See #RuoteStomp::Listener and #RuoteStomp::Participant for detailed
12
+ # documentation on using each of them.
13
+ #
14
+ # == Stomp Notes
15
+ #
16
+ # RuoteAmqp uses durable queues and persistent messages by default, to ensure
17
+ # no messages get lost along the way and that running expressions doesn't have
18
+ # to be restarted in order for messages to be resent. <- not exactly sure yet
19
+ # how RuoteStomp will handle durable queues, messages are persisted.
20
+ #
21
+ module RuoteStomp
22
+
23
+ autoload 'ParticipantProxy', 'ruote-stomp/participant'
24
+
25
+ autoload 'Receiver', 'ruote-stomp/receiver'
26
+ autoload 'WorkitemListener', 'ruote-stomp/workitem_listener'
27
+ autoload 'LaunchitemListener', 'ruote-stomp/launchitem_listener'
28
+
29
+ class << self
30
+
31
+ attr_writer :use_persistent_messages
32
+
33
+ # Whether or not to use persistent messages (true by default)
34
+ def use_persistent_messages?
35
+ @use_persistent_messages = true if @use_persistent_messages.nil?
36
+ @use_persistent_messages
37
+ end
38
+
39
+ # Ensure the Stomp connection is started
40
+ def start!
41
+ return if started?
42
+
43
+ mutex = Mutex.new
44
+ cv = ConditionVariable.new
45
+
46
+ Thread.main[:ruote_stomp_connection] = Thread.new do
47
+ Thread.abort_on_exception = true
48
+
49
+ begin
50
+ $stomp = Stomp::Client.new STOMP.settings[:user],
51
+ STOMP.settings[:passcode],
52
+ STOMP.settings[:host],
53
+ STOMP.settings[:port],
54
+ STOMP.settings[:reliable]
55
+ if $stomp
56
+ started!
57
+ cv.signal
58
+ end
59
+ rescue
60
+ raise RuntimeError, "Failed to connect to Stomp server."
61
+ end
62
+ end
63
+
64
+ mutex.synchronize { cv.wait(mutex) }
65
+
66
+ # Stomp equivalent?
67
+ #MQ.prefetch(1)
68
+
69
+ yield if block_given?
70
+ end
71
+
72
+ # Check whether the AMQP connection is started
73
+ def started?
74
+ Thread.main[:ruote_stomp_started] == true
75
+ end
76
+
77
+ def started! #:nodoc:
78
+ Thread.main[:ruote_stomp_started] = true
79
+ end
80
+
81
+ # Close down the Stomp connections
82
+ def stop!
83
+ return unless started?
84
+ $stomp.close
85
+ Thread.main[:ruote_stomp_connection].join
86
+ Thread.main[:ruote_stomp_started] = false
87
+ end
88
+ end
89
+ end
90
+
91
+ module STOMP
92
+ def self.settings
93
+ @settings ||= {:host => "localhost", :port => "61613", :reliable => false}
94
+ end
95
+ end
96
+
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+
5
+ s.name = 'ruote-stomp'
6
+ s.version = File.read('lib/ruote-stomp/version.rb').match(/VERSION = '([^']+)'/)[1]
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = [ 'Kit Plummer' ]
9
+ s.email = [ 'kplummer@maestrodev.com' ]
10
+ s.homepage = 'http://ruote.rubyforge.org'
11
+ s.rubyforge_project = 'ruote'
12
+ s.summary = 'Stomp participant/listener pair for ruote 2.2'
13
+ s.description = %{
14
+ Stomp participant/listener pair for ruote 2.2 - ported from ruote-amqp.
15
+ }
16
+
17
+ #s.files = `git ls-files`.split("\n")
18
+ s.files = Dir[
19
+ 'Rakefile',
20
+ 'lib/**/*.rb', 'spec/**/*.rb', 'test/**/*.rb',
21
+ '*.gemspec', '*.txt', '*.rdoc', '*.md'
22
+ ]
23
+
24
+ #s.add_runtime_dependency 'stmop'
25
+ s.add_runtime_dependency 'stomp', '1.1.8'
26
+ s.add_runtime_dependency 'ruote', ">= #{s.version}"
27
+
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'rspec', ">= 2.6.3"
30
+ s.add_development_dependency 'stompserver', '~> 0.9.9'
31
+
32
+ s.require_path = 'lib'
33
+ end
34
+
@@ -0,0 +1,68 @@
1
+
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+ describe RuoteStomp::LaunchitemListener do
5
+
6
+ after(:each) do
7
+ purge_engine
8
+ end
9
+
10
+ it 'launches processes' do
11
+
12
+ json = {
13
+ 'definition' => %{
14
+ Ruote.process_definition :name => 'test' do
15
+ sequence do
16
+ echo '${f:foo}'
17
+ end
18
+ end
19
+ },
20
+ 'fields' => { 'foo' => 'bar' }
21
+ }.to_json
22
+
23
+ RuoteStomp::LaunchitemListener.new(@engine)
24
+
25
+ $stomp.publish '/queue/ruote_launchitems', json, { :persistent => true }
26
+ #MQ.queue('ruote_launchitems', :durable => true).publish(json)
27
+
28
+ sleep 0.5
29
+
30
+ @engine.should_not have_errors
31
+ @engine.should_not have_remaining_expressions
32
+
33
+ @tracer.to_s.should == 'bar'
34
+ end
35
+
36
+ it 'discards corrupt process definitions' do
37
+
38
+ json = {
39
+ 'definition' => %{
40
+ I'm a broken process definition
41
+ },
42
+ 'fields' => { 'foo' => 'bar' }
43
+ }.to_json
44
+
45
+ RuoteStomp::LaunchitemListener.new(@engine, :unsubscribe => true)
46
+
47
+ serr = String.new
48
+ err = StringIO.new(serr, 'w+')
49
+ $stderr = err
50
+
51
+ $stomp.publish '/queue/ruote_launchitems', json, { :persistent => true }
52
+
53
+ #MQ.queue('ruote_launchitems', :durable => true).publish(json)
54
+
55
+ sleep 0.5
56
+
57
+ err.close
58
+ $stderr = STDERR
59
+
60
+ @engine.should_not have_errors
61
+ @engine.should_not have_remaining_expressions
62
+
63
+ @tracer.to_s.should == ''
64
+
65
+ serr.should match(/^===/)
66
+ end
67
+ end
68
+
@@ -0,0 +1,208 @@
1
+
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+
4
+
5
+ describe RuoteStomp::ParticipantProxy, :type => :ruote do
6
+
7
+ after(:each) do
8
+ purge_engine
9
+ end
10
+
11
+ it "supports 'forget' as participant attribute" do
12
+
13
+ pdef = ::Ruote.process_definition :name => 'test' do
14
+ sequence do
15
+ stomp :queue => '/queue/test1', :forget => true
16
+ echo 'done.'
17
+ end
18
+ end
19
+
20
+ @engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
21
+
22
+ run_definition(pdef)
23
+
24
+ @tracer.to_s.should == 'done.'
25
+
26
+ begin
27
+ Timeout::timeout(10) do
28
+ @msg = nil
29
+ #MQ.queue('test1', :durable => true).subscribe { |msg| @msg = msg }
30
+ $stomp.subscribe("/queue/test1") do |message|
31
+
32
+ @msg = message.body
33
+ end
34
+
35
+ loop do
36
+ break unless @msg.nil?
37
+ sleep 0.1
38
+ end
39
+ end
40
+ rescue Timeout::Error
41
+ fail "Timeout waiting for message"
42
+ end
43
+
44
+ @msg.should match(/^\{.*\}$/) # JSON message by default
45
+ end
46
+
47
+ it "supports 'forget' as participant option" do
48
+
49
+ pdef = ::Ruote.process_definition :name => 'test' do
50
+ sequence do
51
+ stomp :queue => '/queue/test4'
52
+ echo 'done.'
53
+ end
54
+ end
55
+
56
+ @engine.register_participant(
57
+ :stomp, RuoteStomp::ParticipantProxy, 'forget' => true)
58
+
59
+ run_definition(pdef)
60
+
61
+ @tracer.to_s.should == "done."
62
+
63
+ begin
64
+ Timeout::timeout(5) do
65
+ @msg = nil
66
+ $stomp.subscribe("/queue/test4", {}) do |message|
67
+ @msg = message.body
68
+ end
69
+
70
+ loop do
71
+ break unless @msg.nil?
72
+ sleep 0.1
73
+ end
74
+ end
75
+ rescue Timeout::Error
76
+ fail "Timeout waiting for message"
77
+ end
78
+
79
+ @msg.should match(/^\{.*\}$/) # JSON message by default
80
+ end
81
+
82
+ it "supports custom messages instead of workitems" do
83
+
84
+ pdef = ::Ruote.process_definition :name => 'test' do
85
+ sequence do
86
+ stomp :queue => '/queue/test2', :message => 'foo', :forget => true
87
+ echo 'done.'
88
+ end
89
+ end
90
+
91
+ @engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
92
+
93
+ run_definition(pdef)
94
+
95
+ @tracer.to_s.should == "done."
96
+
97
+ begin
98
+ Timeout::timeout(5) do
99
+ @msg = nil
100
+ #MQ.queue('test2', :durable => true).subscribe { |msg| @msg = msg }
101
+ $stomp.subscribe("/queue/test2", {}) do |message|
102
+ @msg = message.body
103
+ end
104
+ loop do
105
+ break unless @msg.nil?
106
+ sleep 0.1
107
+ end
108
+ end
109
+ rescue Timeout::Error
110
+ fail "Timeout waiting for message"
111
+ end
112
+
113
+ @msg.should == 'foo'
114
+ end
115
+
116
+ it "supports 'queue' as a participant option" do
117
+
118
+ pdef = ::Ruote.process_definition :name => 'test' do
119
+ sequence do
120
+ stomp :forget => true
121
+ echo 'done.'
122
+ end
123
+ end
124
+
125
+ @engine.register_participant(
126
+ :stomp, RuoteStomp::ParticipantProxy, 'queue' => '/queue/test5')
127
+
128
+ run_definition(pdef)
129
+
130
+ @tracer.to_s.should == 'done.'
131
+
132
+ begin
133
+ Timeout::timeout(5) do
134
+ @msg = nil
135
+ #MQ.queue('test5', :durable => true).subscribe { |msg| @msg = msg }
136
+ $stomp.subscribe("/queue/test5", {}) do |message|
137
+ @msg = message.body
138
+ end
139
+ loop do
140
+ break unless @msg.nil?
141
+ sleep 0.1
142
+ end
143
+ end
144
+ rescue Timeout::Error
145
+ fail "Timeout waiting for message"
146
+ end
147
+ end
148
+
149
+ it "passes 'participant_options' over stomp" do
150
+
151
+ pdef = ::Ruote.process_definition :name => 'test' do
152
+ stomp :queue => '/queue/stomp', :forget => true
153
+ end
154
+
155
+ @engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
156
+
157
+ run_definition(pdef)
158
+
159
+ msg = nil
160
+
161
+ begin
162
+ Timeout::timeout(10) do
163
+
164
+ #MQ.queue('test6', :durable => true).subscribe { |m| msg = m }
165
+ $stomp.subscribe("/queue/stomp", {}) do |message|
166
+ msg = message.body
167
+ end
168
+ loop do
169
+ break unless msg.nil?
170
+ sleep 0.1
171
+ end
172
+ end
173
+ rescue Timeout::Error
174
+ fail "Timeout waiting for message"
175
+ end
176
+
177
+ wi = Rufus::Json.decode(msg)
178
+ params = wi['fields']['params']
179
+
180
+ params['queue'].should == '/queue/stomp'
181
+ params['forget'].should == true
182
+ params['participant_options'].should == { 'forget' => false, 'queue' => nil }
183
+ end
184
+
185
+ # it "doesn't create 1 queue instance per delivery" do
186
+ #
187
+ # pdef = ::Ruote.process_definition do
188
+ # stomp :queue => 'test7', :forget => true
189
+ # end
190
+ #
191
+ # mq_count = 0
192
+ # ObjectSpace.each_object($stomp) { |o| stomp_count += 1 }
193
+ #
194
+ # @engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
195
+ #
196
+ # 10.times do
197
+ # run_definition(pdef)
198
+ # end
199
+ #
200
+ # sleep 1
201
+ #
202
+ # count = 0
203
+ # ObjectSpace.each_object($stomp) { |o| count += 1 }
204
+ #
205
+ # count.should == stomp_count + 1
206
+ # end
207
+ end
208
+