ruote-stomp-maestrodev 2.2.2
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.
- data/CHANGELOG.txt +11 -0
- data/CREDITS.txt +33 -0
- data/PostInstall.txt +12 -0
- data/README.rdoc +82 -0
- data/Rakefile +101 -0
- data/TODO.txt +4 -0
- data/lib/ruote-stomp.rb +116 -0
- data/lib/ruote-stomp/launchitem_listener.rb +20 -0
- data/lib/ruote-stomp/participant.rb +212 -0
- data/lib/ruote-stomp/receiver.rb +146 -0
- data/lib/ruote-stomp/version.rb +4 -0
- data/lib/ruote-stomp/workitem_listener.rb +11 -0
- data/ruote-stomp.gemspec +31 -0
- data/spec/launchitem_listener_spec.rb +78 -0
- data/spec/participant_spec.rb +204 -0
- data/spec/receiver_spec.rb +126 -0
- data/spec/ruote_stomp_spec.rb +14 -0
- data/spec/spec_helper.rb +97 -0
- data/spec/support/ruote_helpers.rb +27 -0
- data/spec/support/ruote_matchers.rb +45 -0
- data/spec/workitem_listener_spec.rb +62 -0
- metadata +181 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'ruote-stomp'
|
2
|
+
|
3
|
+
module RuoteStomp
|
4
|
+
|
5
|
+
#
|
6
|
+
# = Stomp Receiver
|
7
|
+
#
|
8
|
+
# Used in conjunction with the RuoteStomp::Participant, the WorkitemListener
|
9
|
+
# subscribes to a specific direct exchange and monitors for
|
10
|
+
# incoming workitems. It expects workitems to arrive serialized as
|
11
|
+
# JSON.
|
12
|
+
#
|
13
|
+
# == Configuration
|
14
|
+
#
|
15
|
+
# Stomp configuration is handled by directly manipulating the values of
|
16
|
+
# the +Stomp.settings+ hash, as provided by the Stomp gem. No
|
17
|
+
# defaults are set by the listener. The only +option+ parsed by
|
18
|
+
# the initializer of the workitem listener is the +queue+ key (Hash
|
19
|
+
# expected). If no +queue+ key is set, the listener will subscribe
|
20
|
+
# to the +ruote_workitems+ direct exchange for workitems, otherwise it will
|
21
|
+
# subscribe to the direct exchange provided.
|
22
|
+
#
|
23
|
+
# == Usage
|
24
|
+
#
|
25
|
+
# Register the engine or storage with the listener:
|
26
|
+
#
|
27
|
+
# RuoteStomp::Receiver.new(engine_or_storage)
|
28
|
+
#
|
29
|
+
# The workitem listener leverages the asynchronous nature of the stomp gem,
|
30
|
+
# so no timers are setup when initialized.
|
31
|
+
#
|
32
|
+
# == Options
|
33
|
+
#
|
34
|
+
# :queue and :launchitems
|
35
|
+
#
|
36
|
+
# See the RuoteStomp::Participant docs for information on sending
|
37
|
+
# workitems out to remote participants, and have them send replies
|
38
|
+
# to the correct direct exchange specified in the workitem
|
39
|
+
# attributes.
|
40
|
+
#
|
41
|
+
class Receiver < Ruote::Receiver
|
42
|
+
|
43
|
+
attr_reader :queue
|
44
|
+
|
45
|
+
# Starts a new Receiver
|
46
|
+
#
|
47
|
+
# Two arguments for this method.
|
48
|
+
#
|
49
|
+
# The first one should be a Ruote::Engine, a Ruote::Storage or
|
50
|
+
# a Ruote::Worker instance.
|
51
|
+
#
|
52
|
+
# The second one is a hash for options. There are two known options :
|
53
|
+
#
|
54
|
+
# :queue for setting the queue on which to listen (defaults to
|
55
|
+
# 'ruote_workitems').
|
56
|
+
#
|
57
|
+
# :ignore_disconnect_on_process => true|false (defauts to false)
|
58
|
+
# processes the message even if the client has disconnected (use in testing only)
|
59
|
+
#
|
60
|
+
# The :launchitems option :
|
61
|
+
#
|
62
|
+
# :launchitems => true
|
63
|
+
# # the receiver accepts workitems and launchitems
|
64
|
+
# :launchitems => false
|
65
|
+
# # the receiver only accepts workitems
|
66
|
+
# :launchitems => :only
|
67
|
+
# # the receiver only accepts launchitems
|
68
|
+
#
|
69
|
+
def initialize(engine_or_storage, opts={})
|
70
|
+
|
71
|
+
super(engine_or_storage)
|
72
|
+
|
73
|
+
@launchitems = opts[:launchitems]
|
74
|
+
ignore_disconnect = opts[:ignore_disconnect_on_process]
|
75
|
+
|
76
|
+
@queue =
|
77
|
+
opts[:queue] ||
|
78
|
+
(@launchitems == :only ? '/queue/ruote_launchitems' : '/queue/ruote_workitems')
|
79
|
+
|
80
|
+
RuoteStomp.start!
|
81
|
+
|
82
|
+
if opts[:unsubscribe]
|
83
|
+
begin
|
84
|
+
$stomp.unsubscribe(@queue)
|
85
|
+
rescue OnStomp::UnsupportedCommandError => e
|
86
|
+
$stderr.puts("Connection does support unsubscribe")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
$stomp.subscribe(@queue) do |message|
|
91
|
+
# Process your message here
|
92
|
+
# Your submitted data is in msg.body
|
93
|
+
if $stomp.connected? && !ignore_disconnect
|
94
|
+
# do nothing, we're going down
|
95
|
+
else
|
96
|
+
handle(message)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def stop
|
102
|
+
RuoteStomp.stop!
|
103
|
+
end
|
104
|
+
|
105
|
+
# (feel free to overwrite me)
|
106
|
+
#
|
107
|
+
def decode_workitem(msg)
|
108
|
+
(Rufus::Json.decode(msg) rescue nil)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def handle(msg)
|
114
|
+
item = decode_workitem(msg.body)
|
115
|
+
return unless item.is_a?(Hash)
|
116
|
+
not_li = ! item.has_key?('definition')
|
117
|
+
return if @launchitems == :only && not_li
|
118
|
+
return unless @launchitems || not_li
|
119
|
+
|
120
|
+
if not_li
|
121
|
+
receive(item) # workitem resumes in its process instance
|
122
|
+
else
|
123
|
+
launch(item) # new process instance launch
|
124
|
+
end
|
125
|
+
|
126
|
+
rescue => e
|
127
|
+
# something went wrong
|
128
|
+
# let's simply discard the message
|
129
|
+
$stderr.puts('=' * 80)
|
130
|
+
$stderr.puts(self.class.name)
|
131
|
+
$stderr.puts("couldn't handle incoming message :")
|
132
|
+
$stderr.puts('')
|
133
|
+
$stderr.puts(msg.inspect)
|
134
|
+
$stderr.puts('')
|
135
|
+
$stderr.puts(Rufus::Json.pretty_encode(item)) rescue nil
|
136
|
+
$stderr.puts('')
|
137
|
+
$stderr.puts(e.inspect)
|
138
|
+
$stderr.puts(e.backtrace)
|
139
|
+
$stderr.puts('=' * 80)
|
140
|
+
end
|
141
|
+
|
142
|
+
def launch(hash)
|
143
|
+
super(hash['definition'], hash['fields'] || {}, hash['variables'] || {})
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/ruote-stomp.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
|
5
|
+
s.name = 'ruote-stomp-maestrodev'
|
6
|
+
s.version = File.read('lib/ruote-stomp/version.rb').match(/VERSION = '([^']+)'/)[1]
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = [ 'Kit Plummer', 'Brian Sam-Bodden' ]
|
9
|
+
s.email = [ 'kplummer@maestrodev.com', 'bsbodden@integrallis.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 = 'Stomp participant/listener pair for ruote 2.2'
|
14
|
+
|
15
|
+
s.files = Dir[
|
16
|
+
'Rakefile',
|
17
|
+
'lib/**/*.rb', 'spec/**/*.rb', 'test/**/*.rb',
|
18
|
+
'*.gemspec', '*.txt', '*.rdoc', '*.md'
|
19
|
+
]
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'onstomp', "~> 1.0.4"
|
22
|
+
s.add_runtime_dependency 'ruote', "~> 2.2.0"
|
23
|
+
s.add_runtime_dependency 'json'
|
24
|
+
s.add_runtime_dependency 'parslet'
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'rspec', ">= 2.6.0"
|
27
|
+
s.add_development_dependency 'stompserver', '~> 0.9.9'
|
28
|
+
|
29
|
+
s.require_path = 'lib'
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe RuoteStomp::LaunchitemListener do
|
4
|
+
|
5
|
+
after(:each) do
|
6
|
+
purge_engine
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'launches processes' do
|
10
|
+
|
11
|
+
json = {
|
12
|
+
'definition' => %{
|
13
|
+
Ruote.process_definition :name => 'test' do
|
14
|
+
sequence do
|
15
|
+
echo '${f:foo}'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
},
|
19
|
+
'fields' => { 'foo' => 'bar' }
|
20
|
+
}.to_json
|
21
|
+
|
22
|
+
RuoteStomp::LaunchitemListener.new(@engine, :ignore_disconnect_on_process => true)
|
23
|
+
|
24
|
+
finished_processing = false
|
25
|
+
|
26
|
+
$stomp.send('/queue/ruote_launchitems', json) do |r|
|
27
|
+
finished_processing = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
Timeout::timeout(10) do
|
32
|
+
while @tracer.to_s.empty?
|
33
|
+
print "*"
|
34
|
+
sleep 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue Timeout::Error
|
38
|
+
fail "Timeout waiting for message"
|
39
|
+
end
|
40
|
+
|
41
|
+
Thread.pass until finished_processing
|
42
|
+
|
43
|
+
@engine.should_not have_errors
|
44
|
+
@engine.should_not have_remaining_expressions
|
45
|
+
@tracer.to_s.should == 'bar'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'discards corrupt process definitions' do
|
49
|
+
|
50
|
+
json = {
|
51
|
+
'definition' => %{
|
52
|
+
I'm a broken process definition
|
53
|
+
},
|
54
|
+
'fields' => { 'foo' => 'bar' }
|
55
|
+
}.to_json
|
56
|
+
|
57
|
+
RuoteStomp::LaunchitemListener.new(@engine, {:unsubscribe => true, :ignore_disconnect_on_process => true})
|
58
|
+
|
59
|
+
serr = String.new
|
60
|
+
err = StringIO.new(serr, 'w+')
|
61
|
+
$stderr = err
|
62
|
+
|
63
|
+
$stomp.send '/queue/ruote_launchitems', json
|
64
|
+
|
65
|
+
sleep 0.5
|
66
|
+
|
67
|
+
err.close
|
68
|
+
$stderr = STDERR
|
69
|
+
|
70
|
+
@engine.should_not have_errors
|
71
|
+
@engine.should_not have_remaining_expressions
|
72
|
+
|
73
|
+
@tracer.to_s.should == ''
|
74
|
+
|
75
|
+
serr.should match(/^===/)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,204 @@
|
|
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
|
+
$stomp.subscribe("/queue/test1") do |message|
|
30
|
+
@msg = message.body
|
31
|
+
end
|
32
|
+
|
33
|
+
loop do
|
34
|
+
break unless @msg.nil?
|
35
|
+
sleep 0.1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue Timeout::Error
|
39
|
+
fail "Timeout waiting for message"
|
40
|
+
end
|
41
|
+
|
42
|
+
@msg.should match(/^\{.*\}$/) # JSON message by default
|
43
|
+
end
|
44
|
+
|
45
|
+
it "supports 'forget' as participant option" do
|
46
|
+
|
47
|
+
pdef = ::Ruote.process_definition :name => 'test' do
|
48
|
+
sequence do
|
49
|
+
stomp :queue => '/queue/test4'
|
50
|
+
echo 'done.'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@engine.register_participant(
|
55
|
+
:stomp, RuoteStomp::ParticipantProxy, 'forget' => true)
|
56
|
+
|
57
|
+
run_definition(pdef)
|
58
|
+
|
59
|
+
@tracer.to_s.should == "done."
|
60
|
+
|
61
|
+
begin
|
62
|
+
Timeout::timeout(5) do
|
63
|
+
@msg = nil
|
64
|
+
$stomp.subscribe("/queue/test4", {}) do |message|
|
65
|
+
@msg = message.body
|
66
|
+
end
|
67
|
+
|
68
|
+
loop do
|
69
|
+
break unless @msg.nil?
|
70
|
+
sleep 0.1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue Timeout::Error
|
74
|
+
fail "Timeout waiting for message"
|
75
|
+
end
|
76
|
+
|
77
|
+
@msg.should match(/^\{.*\}$/) # JSON message by default
|
78
|
+
end
|
79
|
+
|
80
|
+
it "supports custom messages instead of workitems" do
|
81
|
+
|
82
|
+
pdef = ::Ruote.process_definition :name => 'test' do
|
83
|
+
sequence do
|
84
|
+
stomp :queue => '/queue/test2', :message => 'foo', :forget => true
|
85
|
+
echo 'done.'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
|
90
|
+
|
91
|
+
run_definition(pdef)
|
92
|
+
|
93
|
+
@tracer.to_s.should == "done."
|
94
|
+
|
95
|
+
begin
|
96
|
+
Timeout::timeout(5) do
|
97
|
+
@msg = nil
|
98
|
+
$stomp.subscribe("/queue/test2") do |message|
|
99
|
+
@msg = message.body
|
100
|
+
end
|
101
|
+
loop do
|
102
|
+
break unless @msg.nil?
|
103
|
+
sleep 0.1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rescue Timeout::Error
|
107
|
+
fail "Timeout waiting for message"
|
108
|
+
end
|
109
|
+
|
110
|
+
@msg.should == 'foo'
|
111
|
+
end
|
112
|
+
|
113
|
+
it "supports 'queue' as a participant option" do
|
114
|
+
|
115
|
+
pdef = ::Ruote.process_definition :name => 'test' do
|
116
|
+
sequence do
|
117
|
+
stomp :forget => true
|
118
|
+
echo 'done.'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@engine.register_participant(
|
123
|
+
:stomp, RuoteStomp::ParticipantProxy, 'queue' => '/queue/test5')
|
124
|
+
|
125
|
+
run_definition(pdef)
|
126
|
+
|
127
|
+
@tracer.to_s.should == 'done.'
|
128
|
+
|
129
|
+
begin
|
130
|
+
Timeout::timeout(5) do
|
131
|
+
@msg = nil
|
132
|
+
$stomp.subscribe("/queue/test5") do |message|
|
133
|
+
@msg = message.body
|
134
|
+
end
|
135
|
+
loop do
|
136
|
+
break unless @msg.nil?
|
137
|
+
sleep 0.1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Timeout::Error
|
141
|
+
fail "Timeout waiting for message"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "passes 'participant_options' over stomp" do
|
146
|
+
|
147
|
+
pdef = ::Ruote.process_definition :name => 'test' do
|
148
|
+
stomp :queue => '/queue/stomp', :forget => true
|
149
|
+
end
|
150
|
+
|
151
|
+
@engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
|
152
|
+
|
153
|
+
run_definition(pdef)
|
154
|
+
|
155
|
+
msg = nil
|
156
|
+
|
157
|
+
begin
|
158
|
+
Timeout::timeout(10) do
|
159
|
+
|
160
|
+
#MQ.queue('test6', :durable => true).subscribe { |m| msg = m }
|
161
|
+
$stomp.subscribe("/queue/stomp") do |message|
|
162
|
+
msg = message.body
|
163
|
+
end
|
164
|
+
loop do
|
165
|
+
break unless msg.nil?
|
166
|
+
sleep 0.1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
rescue Timeout::Error
|
170
|
+
fail "Timeout waiting for message"
|
171
|
+
end
|
172
|
+
|
173
|
+
wi = Rufus::Json.decode(msg)
|
174
|
+
params = wi['fields']['params']
|
175
|
+
|
176
|
+
params['queue'].should == '/queue/stomp'
|
177
|
+
params['forget'].should == true
|
178
|
+
params['participant_options'].should == { 'forget' => false, 'queue' => nil }
|
179
|
+
end
|
180
|
+
|
181
|
+
# it "doesn't create 1 queue instance per delivery" do
|
182
|
+
#
|
183
|
+
# pdef = ::Ruote.process_definition do
|
184
|
+
# stomp :queue => 'test7', :forget => true
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# mq_count = 0
|
188
|
+
# ObjectSpace.each_object($stomp) { |o| stomp_count += 1 }
|
189
|
+
#
|
190
|
+
# @engine.register_participant(:stomp, RuoteStomp::ParticipantProxy)
|
191
|
+
#
|
192
|
+
# 10.times do
|
193
|
+
# run_definition(pdef)
|
194
|
+
# end
|
195
|
+
#
|
196
|
+
# sleep 1
|
197
|
+
#
|
198
|
+
# count = 0
|
199
|
+
# ObjectSpace.each_object($stomp) { |o| count += 1 }
|
200
|
+
#
|
201
|
+
# count.should == stomp_count + 1
|
202
|
+
# end
|
203
|
+
end
|
204
|
+
|