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 ADDED
@@ -0,0 +1,11 @@
1
+
2
+ = ruote-stomp
3
+
4
+
5
+ == ruote-stomp - 2.2.0 released ?
6
+
7
+ - work has just begun, porting from ruote-amqp
8
+
9
+
10
+
11
+
data/CREDITS.txt ADDED
@@ -0,0 +1,33 @@
1
+
2
+ = CREDITS
3
+
4
+ (probably incomplete, don't hesitate to tell us if a mention is missing)
5
+
6
+ == AUTHORS
7
+
8
+ * Kit Plummer - http://kitplummer.github.com
9
+ * Brian Sam-Bodden = http://bsbodden.github.com
10
+
11
+ == AUTHORS (from ruote-amqp)
12
+
13
+ * Kenneth Kalmer - http://www.opensourcery.co.za/
14
+ * John Mettraux - https://github.com/jmettraux
15
+
16
+
17
+ == CONTRIBUTORS (from ruote-amqp)
18
+
19
+ * Mario Camou
20
+ * Sean Johnson - https://github.com/belucid
21
+ * Victor Liu - https://github.com/pennymax
22
+ * weifeng - https://github.com/weifeng365
23
+ * David Greaves - https://github.com/lbt
24
+ * Hartog C. de Mik - https://github.com/coffeeaddict
25
+ * Torsten Schoenebaum - https://github.com/tosch
26
+ * Jordan Ritter - https://github.com/jpr5
27
+ * Charles Magid - https://github.com/ChasManRors
28
+ * Marc Mauger - https://github.com/simianarmy
29
+ * Jason - https://github.com/asm
30
+
31
+
32
+ == FEEDBACK
33
+
data/PostInstall.txt ADDED
@@ -0,0 +1,12 @@
1
+
2
+ For more information on ruote-stomp, see http://github.com/maestrodev/ruote-stomp
3
+
4
+ Please note that this gem requires access to an Stomp broker and a good
5
+ understanding of Stomp and remote participants in general.
6
+
7
+ Join us in #ruote on Freenode or on the openwfe-users Google Group to discuss
8
+ it's uses.
9
+
10
+ You might also want to look at daemon-kit for help with writing external
11
+ participants that communicate with ruote via Stomp.
12
+
data/README.rdoc ADDED
@@ -0,0 +1,82 @@
1
+
2
+ = ruote-stomp
3
+
4
+ * http://github.com/kitplummer/ruote-stomp
5
+
6
+ == DESCRIPTION:
7
+
8
+ ruote-stomp provides a Stomp participant/listener pair that allows you to
9
+ distribute workitems out to Stomp consumers for processing, as well as launching
10
+ processes over Stomp.
11
+
12
+ To learn more about remote participants in ruote please see
13
+ http://ruote.rubyforge.org/part_implementations.html
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ * Flexible participant for sending workitems
18
+ * Flexible receiver for receiving replies
19
+ * Flexible launch item listener for launching processes over Stomp
20
+ * Fully evented (thanks to the onstomp gem)
21
+
22
+ == SYNOPSIS:
23
+
24
+ Please review the code for information (rdocs to be updated soon.)
25
+
26
+ == REQUIREMENTS:
27
+
28
+ * ruote[http://ruote.rubyforge.org] 2.2.0 or later
29
+ * onstomp[https://github.com/meadvillerb/onstomp] 1.0.4 or later
30
+ * a server that supports Stomp (stompserver, ActiveMQ, RabbitMQ)
31
+
32
+ == INSTALL:
33
+
34
+ Please be sure to have read the requirements section above
35
+
36
+ * sudo gem install ruote-stomp
37
+
38
+ == TESTS:
39
+
40
+ To run the tests you need the following requirements met, or the testing environment
41
+ will fail horribly (or simply get stuck without output).
42
+
43
+ === Stomp server
44
+
45
+ I've tested it with stompserver and ActiveMQ.
46
+
47
+ I'll work on getting the Ruby stompserver setup to start/shutdown with the tests.
48
+
49
+ == DAEMON-KIT:
50
+
51
+ Will work on adding Daemon-Kit support for ruote-stomp next. :)
52
+
53
+ == LICENSE:
54
+
55
+ (The MIT License)
56
+
57
+ Copyright (c) 2010-2011 Kit Plummer (and Kenneth Kalmer for
58
+ ruote-amqp, which this works is based on)
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining
61
+ a copy of this software and associated documentation files (the
62
+ 'Software'), to deal in the Software without restriction, including
63
+ <<<<<<< Updated upstream
64
+ without limitation the rights to use, copy, modify, merge, send,
65
+ distribute, sublicense, and/or sell copies of the Software, and to
66
+ =======
67
+ without limitation the rights to use, copy, modify, merge, publish,
68
+ distribute, sublicense, and/or sell copies of tshe Software, and to
69
+ >>>>>>> Stashed changes
70
+ permit persons to whom the Software is furnished to do so, subject to
71
+ the following conditions:
72
+
73
+ The above copyright notice and this permission notice shall be
74
+ included in all copies or substantial portions of the Software.
75
+
76
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,101 @@
1
+
2
+ $:.unshift('.') # 1.9.2
3
+
4
+ require 'rubygems'
5
+ require 'rubygems/user_interaction' if Gem::RubyGemsVersion == '1.5.0'
6
+
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rake/task'
10
+
11
+
12
+ #
13
+ # clean
14
+
15
+ CLEAN.include('pkg', 'rdoc')
16
+
17
+
18
+ #
19
+ # test / spec
20
+
21
+ task :spec do
22
+
23
+ sh 'rspec spec/'
24
+ end
25
+
26
+ task :test => [ :spec ]
27
+ task :default => [ :spec ]
28
+
29
+
30
+ #
31
+ # gem
32
+
33
+ GEMSPEC_FILE = Dir['*.gemspec'].first
34
+ GEMSPEC = eval(File.read(GEMSPEC_FILE))
35
+ GEMSPEC.validate
36
+
37
+
38
+ desc %{
39
+ builds the gem and places it in pkg/
40
+ }
41
+ task :build do
42
+
43
+ sh "gem build #{GEMSPEC_FILE}"
44
+ sh "mkdir pkg" rescue nil
45
+ sh "mv #{GEMSPEC.name}-#{GEMSPEC.version}.gem pkg/"
46
+ end
47
+
48
+ desc %{
49
+ builds the gem and pushes it to rubygems.org
50
+ }
51
+ task :push => :build do
52
+
53
+ sh "gem push pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.gem"
54
+ end
55
+
56
+
57
+ #
58
+ # rabbitmq preparation
59
+
60
+ desc %{
61
+ prepare RabbitMQ (vhost, user, perms)
62
+ }
63
+ task :prepare do
64
+
65
+ sh "rabbitmqctl add_vhost ruote-test"
66
+ sh "rabbitmqctl add_user ruote ruote"
67
+ sh "rabbitmqctl set_permissions -p ruote-test ruote '.*' '.*' '.*'"
68
+ end
69
+
70
+
71
+ #
72
+ # rdoc
73
+ #
74
+ # make sure to have rdoc 2.5.x to run that
75
+
76
+ # Rake::RDocTask.new do |rd|
77
+ #
78
+ # rd.main = 'README.rdoc'
79
+ # rd.rdoc_dir = 'rdoc'
80
+ #
81
+ # rd.rdoc_files.include(
82
+ # 'README.rdoc', 'CHANGELOG.txt', 'CREDITS.txt', 'lib/**/*.rb')
83
+ #
84
+ # rd.title = "#{GEMSPEC.name} #{GEMSPEC.version}"
85
+ # end
86
+
87
+
88
+ #
89
+ # upload_rdoc
90
+
91
+ # desc %{
92
+ # upload the rdoc to rubyforge
93
+ # }
94
+ # task :upload_rdoc => [ :clean, :rdoc ] do
95
+ #
96
+ # account = 'jmettraux@rubyforge.org'
97
+ # webdir = '/var/www/gforge-projects/ruote'
98
+ #
99
+ # sh "rsync -azv -e ssh rdoc/#{GEMSPEC.name}_rdoc #{account}:#{webdir}/"
100
+ # end
101
+
data/TODO.txt ADDED
@@ -0,0 +1,4 @@
1
+
2
+ [ ] have a class method ParticipantProxy.stop_all ?
3
+ [ ] use Ruote::Workitem #as_json and #from_json(s)
4
+
@@ -0,0 +1,116 @@
1
+ require 'onstomp'
2
+
3
+ require 'ruote-stomp/version'
4
+
5
+ #
6
+ # Stomp participant and listener pair for ruote.
7
+ #
8
+ # == Documentation
9
+ #
10
+ # See #RuoteStomp::Listener and #RuoteStomp::Participant for detailed
11
+ # documentation on using each of them.
12
+ #
13
+ # == Stomp Notes
14
+ #
15
+ # RuoteAmqp uses durable queues and persistent messages by default, to ensure
16
+ # no messages get lost along the way and that running expressions doesn't have
17
+ # to be restarted in order for messages to be resent. <- not exactly sure yet
18
+ # how RuoteStomp will handle durable queues, messages are persisted.
19
+ #
20
+ module RuoteStomp
21
+
22
+ autoload 'ParticipantProxy', 'ruote-stomp/participant'
23
+ autoload 'Receiver', 'ruote-stomp/receiver'
24
+ autoload 'WorkitemListener', 'ruote-stomp/workitem_listener'
25
+ autoload 'LaunchitemListener', 'ruote-stomp/launchitem_listener'
26
+
27
+ class << self
28
+
29
+ attr_writer :use_persistent_messages
30
+
31
+ # Whether or not to use persistent messages (true by default)
32
+ def use_persistent_messages?
33
+ @use_persistent_messages = true if @use_persistent_messages.nil?
34
+ @use_persistent_messages
35
+ end
36
+
37
+ # Ensure the Stomp connection is started
38
+ def start!
39
+ return if started?
40
+ mutex = Mutex.new
41
+ cv = ConditionVariable.new
42
+ Thread.main[:ruote_stomp_connection] = Thread.new do
43
+ Thread.abort_on_exception = true
44
+
45
+ begin
46
+ if STOMP.settings[:ssl]
47
+ $stomp = OnStomp::Client.new(create_connection_uri(STOMP.settings),
48
+ :ssl => {
49
+ :ca_file => STOMP.settings[:cert],
50
+ :verify_mode => OpenSSL::SSL::VERIFY_NONE
51
+ }
52
+ )
53
+ $stomp.connect
54
+ else
55
+ $stomp = OnStomp.connect create_connection_uri(STOMP.settings)
56
+ end
57
+
58
+ if $stomp && $stomp.connected?
59
+ started!
60
+ cv.signal
61
+ end
62
+ rescue Exception => e
63
+ raise RuntimeError, "Failed to connect to Stomp server. (#{e.message})"
64
+ end
65
+ end
66
+
67
+ mutex.synchronize { cv.wait(mutex) }
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.disconnect
85
+ Thread.main[:ruote_stomp_connection].join
86
+ Thread.main[:ruote_stomp_started] = false
87
+ end
88
+
89
+ protected
90
+
91
+ def create_connection_uri(config={})
92
+ config = config.map { |n,v| {n,v.to_s} }.reduce(:merge)
93
+ user = config[:user]
94
+ passcode = config[:passcode]
95
+ host = config[:host]
96
+ port = config[:port]
97
+ ssl = config[:ssl] || false
98
+ cert = config[:cert] || ""
99
+
100
+ # construct the connection URI
101
+ user_and_password = [user,passcode].reject{|e| e.nil? || e.empty?}.join(":")
102
+ host_and_port = [host,port].reject{|e| e.nil? || e.empty?}.join(":")
103
+ uri = [host_and_port, user_and_password].reject{|e| e.nil? || e.empty?}.reverse.join("@")
104
+ protocol = ['stomp', ssl, '://'].reject{|e| e.nil? || e.empty?}.join
105
+
106
+ "#{protocol}#{uri}"
107
+ end
108
+ end
109
+ end
110
+
111
+ module STOMP
112
+ def self.settings
113
+ @settings ||= {:host => "localhost", :port => "61613"}
114
+ end
115
+ end
116
+
@@ -0,0 +1,20 @@
1
+ module RuoteStomp
2
+
3
+ #
4
+ # Got replaced by RuoteStomp::Receiver
5
+ #
6
+ # This class is kept for backward compatibility.
7
+ #
8
+ class LaunchitemListener < ::RuoteStomp::Receiver
9
+
10
+ # Start a new LaunchItem listener
11
+ #
12
+ # @param [ Ruote::Engine, Ruote::Storage ] A configured ruote engine or storage instance
13
+ # @param opts :queue / :unsubscribe
14
+ #
15
+ def initialize(engine_or_storage, opts={})
16
+ super(engine_or_storage, opts.merge(:launchitems => :only))
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,212 @@
1
+ require 'ruote/part/local_participant'
2
+ require 'ruote-stomp'
3
+
4
+ module RuoteStomp
5
+ #
6
+ # = Stomp Participants
7
+ #
8
+ # The RuoteStomp::ParticipantProxy allows you to send workitems (serialized as
9
+ # JSON) or messages to any Stomp queues right from the process
10
+ # definition. When combined with the RuoteStomp::Receiver you can easily
11
+ # leverage an extremely powerful local/remote participant
12
+ # combinations.
13
+ #
14
+ # For local/remote participants The local part of the
15
+ # RuoteStomp::ParticipantProxy relies on the presence of a
16
+ # RuoteStomp::Receiver. Workitems are sent to the remote participant
17
+ # and the local part does not normally reply to the engine. Instead
18
+ # the engine will continue when a reply is received on the
19
+ # 'ruote_workitems' queue (see RuoteStomp::Receiver).
20
+ #
21
+ # Of course, the standard :forget => true format can be used even
22
+ # with remote particpants and :forget can even be set as a default in
23
+ # the options.
24
+ #
25
+ # [NOTE: Working this port next!!!]
26
+ # A simple way to create a remote participant to act upon workitems
27
+ # is to use the daemon-kit ruote responder.
28
+ #
29
+ # Simple Stomp messages are treated as 'fire and forget' and the flow
30
+ # will continue when the local participant has queued the message
31
+ # for sending. (As there is no meaningful way to receive a workitem
32
+ # in reply).
33
+ #
34
+ # == Configuration
35
+ #
36
+ # Stomp configuration is handled by directly manipulating the
37
+ # values of the +Stomp.settings+ hash, as provided by the Stomp
38
+ # gem. No Stomp defaults are set by the participant.
39
+ #
40
+ # == Usage
41
+ #
42
+ # Define the queue used by an AMQP participant :
43
+ #
44
+ # engine.register_participant(
45
+ # :delete_user, RuoteStomp::ParticipantProxy, 'queue' => 'user_manager')
46
+ #
47
+ # Sending a workitem to the remote participant defined above:
48
+ #
49
+ # Ruote.process_definition do
50
+ # sequence do
51
+ # delete_user
52
+ # end
53
+ # end
54
+ #
55
+ # Let the local participant reply to the engine without involving
56
+ # the receiver
57
+ #
58
+ # Ruote.process_definition do
59
+ # sequence do
60
+ # delete_user :forget => true
61
+ # end
62
+ # end
63
+ #
64
+ # Setting up the participant in a slightly more 'raw' way:
65
+ #
66
+ # engine.register_participant(
67
+ # :stomp, RuoteStomp::ParticipantProxy )
68
+ #
69
+ # Sending a workitem to a specific queue:
70
+ #
71
+ # Ruote.process_definition do
72
+ # sequence do
73
+ # stomp :queue => 'test', 'command' => '/run/regression_test'
74
+ # end
75
+ # end
76
+ #
77
+ # Setup a 'fire and forget' participant that always replies to the
78
+ # engine:
79
+ #
80
+ # engine.register_participant(
81
+ # :jfdi, RuoteStomp::ParticipantProxy, 'forget' => true )
82
+ #
83
+ # Sending a message example to a specific queue (both steps are
84
+ # equivalent):
85
+ #
86
+ # Ruote.process_definition do
87
+ # sequence do
88
+ # stomp :queue => 'test', :message => 'foo'
89
+ # stomp :queue => 'test', :message => 'foo', :forget => true
90
+ # end
91
+ # end
92
+ #
93
+ #
94
+ # == Stomp notes
95
+ #
96
+ # The direct exchanges are always marked as durable by the
97
+ # participant, and messages are marked as persistent by default (see
98
+ # #RuoteStomp)
99
+ #
100
+ class ParticipantProxy
101
+
102
+ include Ruote::LocalParticipant
103
+
104
+ # The following parameters are used in the process definition.
105
+ #
106
+ # An options hash with the same keys to provide defaults is
107
+ # accepted at registration time (see above).
108
+ #
109
+ # * :queue => (string) The Stomp queue used by the remote participant.
110
+ # nil by default.
111
+ # * :forget => (bool) Whether the flow should block until the remote
112
+ # participant replies.
113
+ # false by default
114
+ #
115
+ def initialize(options)
116
+ @options = {
117
+ 'queue' => nil,
118
+ 'forget' => false,
119
+ }.merge(options.inject({}) { |h, (k, v)|
120
+ h[k.to_s] = v; h
121
+ })
122
+ #
123
+ # the inject is here to make sure that all options have String keys
124
+ end
125
+
126
+ # Process the workitem at hand. By default the workitem will be
127
+ # sended to the direct exchange specified in the +queue+
128
+ # workitem parameter. You can specify a +message+ workitem
129
+ # parameter to have that sent instead of the workitem.
130
+ #
131
+ def consume(workitem)
132
+
133
+ RuoteStomp.start!
134
+ target_queue = determine_queue(workitem)
135
+
136
+ raise 'no queue specified (outbound delivery)' unless target_queue
137
+
138
+ forget = determine_forget(workitem)
139
+
140
+ opts = {
141
+ :persistent => RuoteStomp.use_persistent_messages?,
142
+ :content_type => 'application/json' }
143
+
144
+ if message = workitem.fields['message'] || workitem.params['message']
145
+
146
+ forget = true # sending a message implies 'forget' => true
147
+ $stomp.send target_queue, message, opts
148
+ else
149
+ $stomp.send target_queue, encode_workitem(workitem), opts
150
+ end
151
+
152
+ reply_to_engine(workitem) if forget
153
+ end
154
+
155
+ # (Stops the underlying queue subscription)
156
+ #
157
+ def stop
158
+ RuoteStomp.stop!
159
+ end
160
+
161
+ def cancel(fei, flavour)
162
+ #
163
+ # TODO : sending a cancel item is not a bad idea, especially if the
164
+ # job done over the stomp fence lasts...
165
+ #
166
+ end
167
+
168
+ # [NOT sure about this behavior with Stomp yet. Need to dive.]
169
+
170
+ def do_not_thread
171
+ true
172
+ end
173
+
174
+ private
175
+
176
+ def determine_forget(workitem)
177
+ return workitem.params['forget'] if workitem.params.has_key?('forget')
178
+ return @options['forget'] if @options.has_key?('forget')
179
+ false
180
+ end
181
+
182
+ def determine_queue(workitem)
183
+ workitem.params['queue'] || @options['queue']
184
+ end
185
+
186
+ # Encodes the workitem as JSON. Makes sure to add to the field 'params'
187
+ # an entry named 'participant_options' which contains the options of
188
+ # this participant.
189
+ #
190
+ def encode_workitem(wi)
191
+ wi.params['participant_options'] = @options
192
+ Rufus::Json.encode(wi.to_h)
193
+ end
194
+ end
195
+
196
+ #
197
+ # Kept for backward compatibility.
198
+ #
199
+ # You should use RuoteStomp::ParticipantProxy.
200
+ #
201
+ class Participant < ParticipantProxy
202
+
203
+ def initialize(options)
204
+ puts '=' * 80
205
+ puts "RuoteStomp::Participant will be deprecated soon (2.1.12)"
206
+ puts "please use RuoteStomp::ParticipantProxy instead"
207
+ puts '=' * 80
208
+ super
209
+ end
210
+ end
211
+ end
212
+