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