ruote-amqp 2.2.0 → 2.3.0

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 CHANGED
@@ -2,6 +2,11 @@
2
2
  = ruote-amqp
3
3
 
4
4
 
5
+ == ruote-amqp - 2.3.0 released 2012/09/02
6
+
7
+ - complete rework to better adhere to the AMQP philosophy
8
+
9
+
5
10
  == ruote-amqp - 2.2.0 released 2011/03/01
6
11
 
7
12
  - receiver : exposing #decode_workitem for overwriting
data/CREDITS.txt CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  == CONTRIBUTORS
14
14
 
15
+ * Jiří Kubíček - https://github.com/kubicek
15
16
  * Mario Camou
16
17
  * Sean Johnson - https://github.com/belucid
17
18
  * Victor Liu - https://github.com/pennymax
@@ -27,3 +28,6 @@
27
28
 
28
29
  == FEEDBACK
29
30
 
31
+ * Jim Li - https://github.com/marsbomber - auto_recovery and co
32
+ * Marco Sehrer - https://github.com/pixelvitamina - ruote 2.2 vs 2.3
33
+
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ Copyright (c) 2010-2012 Kenneth Kalmer
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+
2
+ # ruote-amqp
3
+
4
+ ruote-amqp is a set of classes that let a ruote engine publish and/or receive messages over AMQP.
5
+
6
+ The most common use case is publishing workitems for processing by AMQP consumers and eventually receiving them back to resume the flow.
7
+
8
+ Another use case would be to listen on an AMQP queue for workflow launch requests.
9
+
10
+ Listening for arbitrary AMQP messages before resuming a flow (ambush/alert) is also possible.
11
+
12
+
13
+ ## usage
14
+
15
+ ### Ruote::Amqp::Participant
16
+
17
+ Publishing messages
18
+
19
+ ```ruby
20
+ $dashboard.register(
21
+ :toto,
22
+ Ruote::Amqp::Participant,
23
+ :exchange => [ 'direct', '' ],
24
+ :routing_key => 'alpha')
25
+
26
+ pdef = Ruote.define do
27
+ toto
28
+ end
29
+
30
+ $dashboard.launch(pdef)
31
+
32
+ # ...
33
+ ```
34
+
35
+ ### Ruote::Amqp::AlertParticipant
36
+
37
+ Ambushing messages from a process definition. The alert participant when
38
+ receiving a workitem starts waiting for the next message on a given queue. When
39
+ the message arrives, it responds to the engine (with a
40
+
41
+ ```ruby
42
+ $dashboard.register(
43
+ :wait_for_info,
44
+ Ruote::Amqp::AlertParticipant,
45
+ :queue => 'info')
46
+
47
+ pdef = Ruote.define do
48
+ # ... before
49
+ wait_for_job # flows wait for first message on 'info' queue
50
+ # ... after
51
+ end
52
+ ```
53
+
54
+ By default it waits for 1 message that it places in the "amqp_message" field
55
+ of the workitem going back to the engine.
56
+
57
+ One can override the #handle method to change the way the workitem is modified
58
+ according to the message.
59
+
60
+ It's also OK to override the #on_workitem method of this participant if one
61
+ waits to wait for more than 1 message.
62
+
63
+ See the AlertParticipant rdoc for more.
64
+
65
+ ### Ruote::Amqp::Receiver
66
+
67
+ Receiving messages.
68
+
69
+ A receiver is a ruote service subscribed to a queue. When a message comes on
70
+ the queue, the receiver will look at it and, according to the payload, either
71
+ launch a new workflow instance, resume a currently workflow instance segment
72
+ or pass an error back from a participant to the engine.
73
+
74
+ (In fact the resume workflow / pass participant error back to the engine are
75
+ closely related)
76
+
77
+ (NTS: at some point, receivers should be able to deal with "cancel messages")
78
+
79
+ ```ruby
80
+ # A simple coupling between participant "toto" and a receiver via AMQP
81
+ #
82
+ # A real world example would have toto publishing somewhere, the message
83
+ # getting fetched by the real (remote) participant and then handed back
84
+ # on the queue the receiver is subscribed to.
85
+
86
+ $dashboard.register(
87
+ :toto,
88
+ Ruote::Amqp::Participant,
89
+ :exchange => [ 'direct', '' ],
90
+ :routing_key => 'alpha')
91
+
92
+ receiver = Ruote::Amqp::Receiver.new(
93
+ $dashboard, AMQP::Channel.new.queue('alpha'))
94
+
95
+ # ...
96
+ ```
97
+
98
+ ### Controlling the connection (AMQP session)
99
+
100
+ The Ruote::Amqp module has a handy singleton for connections (actually
101
+ AMQP::Session instances).
102
+
103
+ ```ruby
104
+ # (before registering participants)
105
+
106
+ Ruote::Amqp.session = AMQP.connect(:auto_recovery => true) do |con|
107
+ con.on_recovery do |con|
108
+ puts "Recovered..."
109
+ end
110
+ connection.on_tcp_connection_loss do |con, settings|
111
+ puts "Reconnecting... please wait"
112
+ conn.reconnect(false, 20)
113
+ end
114
+ end
115
+ ```
116
+
117
+ When a participant tries to connect to AMQP, it will automatically use the value in Ruote::Amqp.session (else it will set up a new connection).
118
+
119
+ The receivers expect a queue when they are set up, feel free to set Ruote::Amqp.session, then use it when instantiating receivers (the participant will follow suit).
120
+
121
+ If you want a different way of connecting to AMQP for the participants, you can override their #amqp_connect methods (or pass them AMQP connection settings when registering them).
122
+
123
+
124
+ ## requirements
125
+
126
+ * ruote[http://ruote.rubyforge.org] 2.3.0 or later
127
+ * amqp[http://rubyamqp.info/] 0.9.0 or later
128
+ * rabbitmq[http://www.rabbitmq.com/] 2.2.0 or later
129
+
130
+
131
+ ## install
132
+
133
+ Please be sure to have read the requirements section above
134
+
135
+ gem install ruote-amqp
136
+
137
+ or via your Gemfile (thanks [bundler](http://gembundler.com)).
138
+
139
+
140
+ ## tests / specs
141
+
142
+ To run the tests you need the following requirements met, or the testing environment will fail horribly (or simply get stuck without output).
143
+
144
+
145
+ ### RabbitMQ vhost
146
+
147
+ The tests use dedicated vhost on a running AMQP broker. To configure RabbitMQ
148
+ you can run the following commands (the RabbitMQ server must be running):
149
+
150
+ $ rabbitmqctl add_vhost ruote-test
151
+ $ rabbitmqctl add_user ruote ruote
152
+ $ rabbitmqctl set_permissions -p ruote-test ruote '.*' '.*' '.*'
153
+
154
+ or by running:
155
+
156
+ $ rake prepare
157
+
158
+
159
+ If you need to change the AMQP configuration used by the tests, edit the
160
+ +spec/spec_helper.rb+ file.
161
+
162
+
163
+ ## daemon-kit
164
+
165
+ Kenneth Kalmer, the original author of the ruote-amqp gem is also the author of [DaemonKit](https://github.com/kennethkalmer/daemon-kit) a library/toolbox for building daemons.
166
+
167
+ It used to be the preferred way to wrap remote participants (as daemons) but lately Kenneth hasn't had much time for support. It's still full of excellent ideas.
168
+
169
+
170
+ ## license
171
+
172
+ MIT, see LICENSE.txt
173
+
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rubygems/user_interaction' if Gem::RubyGemsVersion == '1.5.0'
6
6
 
7
7
  require 'rake'
8
8
  require 'rake/clean'
9
- require 'rake/rdoctask'
9
+ #require 'rake/rdoctask'
10
10
 
11
11
 
12
12
  #
@@ -68,34 +68,36 @@ task :prepare do
68
68
  end
69
69
 
70
70
 
71
+ ##
72
+ ## rdoc
73
+ ##
74
+ ## make sure to have rdoc 2.5.x to run that
71
75
  #
72
- # rdoc
76
+ #Rake::RDocTask.new do |rd|
73
77
  #
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
-
78
+ # rd.main = 'README.rdoc'
79
+ # rd.rdoc_dir = 'rdoc'
88
80
  #
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
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
+ #
102
+ # leverarge rdoc.info instead
101
103
 
data/TODO.txt CHANGED
@@ -6,3 +6,11 @@
6
6
  [ ] have a class method ParticipantProxy.stop_all ?
7
7
  [ ] use Ruote::Workitem #as_json and #from_json(s)
8
8
 
9
+ *** 2.3.0
10
+
11
+ [x] https://gist.github.com/1944228
12
+ [ ] ON_CANCEL !
13
+
14
+ [ ] incorporate
15
+ https://github.com/marsbomber/ruote-amqp/commit/0f36a41f4a0254847a7b9a7c4b2098c8164f21f3
16
+
@@ -0,0 +1,146 @@
1
+ #--
2
+ # Copyright (c) 2010-2012, Kenneth Kalmer, John Mettraux.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+
24
+ module Ruote::Amqp
25
+
26
+ #
27
+ # The alert participant, when invoked from a process instance will
28
+ # lay in wait for the next message on a given queue. As soon as the message
29
+ # comes in, it will pack it in the workitem fields and let the process
30
+ # definition resume.
31
+ #
32
+ # @dashboard.register(
33
+ # :wait_for_info,
34
+ # Ruote::Amqp::AlertParticipant,
35
+ # :queue => 'info')
36
+ #
37
+ # pdef = Ruote.define do
38
+ # wait_for_job
39
+ # # ... the rest of the flow
40
+ # end
41
+ #
42
+ # == configuration
43
+ #
44
+ # This class is mostly a subclass of Ruote::Amqp::Participant, it accepts
45
+ # the same configuration options (but has no need for 'exchange'). It
46
+ # accepts a 'queue' option in the form [ 'queue_name', { queue options } ].
47
+ #
48
+ #
49
+ # == overriding #handle(header, payload)
50
+ #
51
+ # The default implementation for this method is:
52
+ #
53
+ # def handle(header, payload)
54
+ # workitem.fields['amqp_message'] = [ header.to_hash, payload ]
55
+ # end
56
+ #
57
+ # One is free to override it:
58
+ #
59
+ # class MyAlertParticipant < Ruote::Amqp::AlertParticipant
60
+ # def handle(header, payload)
61
+ # fields = Rufus::Json.decode(payload)
62
+ # workitem.fields.merge!(fields)
63
+ # end
64
+ # end
65
+ #
66
+ #
67
+ # == overriding #on_workitem
68
+ #
69
+ # Out of the box, the alert participant listens for 1 message on 1 queue.
70
+ # It's not too difficult to change that.
71
+ #
72
+ # Resuming after 3 messages:
73
+ #
74
+ # class MyAlertParticipant < Ruote::Amqp::AlertParticipant
75
+ #
76
+ # def on_workitem
77
+ #
78
+ # messages = []
79
+ #
80
+ # queue.subscribe { |header, payload |
81
+ #
82
+ # messages << payload
83
+ #
84
+ # if messages.size > 2
85
+ # queue.unsubscribe
86
+ # workitem.fields['messages'] = messages
87
+ # reply # let the flow resume
88
+ # end
89
+ # }
90
+ # end
91
+ # end
92
+ #
93
+ # Observing 2 queues:
94
+ #
95
+ # class MyAlertParticipant < Ruote::Amqp::AlertParticipant
96
+ #
97
+ # def on_workitem
98
+ #
99
+ # messages = []
100
+ #
101
+ # q0 = channel.queue('zero')
102
+ # q1 = channel.queue('one')
103
+ #
104
+ # [ q0, q1 ].subscribe { |header, payload |
105
+ # messages << payload
106
+ # }
107
+ #
108
+ # sleep 1.0 while messages.size < 2
109
+ #
110
+ # reply # let the flow resume
111
+ # end
112
+ # end
113
+ #
114
+ class AlertParticipant < Participant
115
+
116
+ def on_workitem
117
+
118
+ queue.subscribe { |header, payload|
119
+
120
+ queue.unsubscribe
121
+ handle(header, payload)
122
+ reply
123
+ }
124
+ end
125
+
126
+ protected
127
+
128
+ # Called when the AMQP message comes in. This default implementation
129
+ # stuffs the AMQP [ header, payload ] into an 'amqp_message' workitem
130
+ # field.
131
+ #
132
+ def handle(header, payload)
133
+
134
+ workitem.fields['amqp_message'] = [ header.to_hash, payload ]
135
+ end
136
+
137
+ # Looks at the configuration options ('connection' and 'queue') and
138
+ # returns the queue the participant will fetch a message from.
139
+ #
140
+ def queue
141
+
142
+ @queue ||= channel.queue(*(opt('queue') || [ '' ]))
143
+ end
144
+ end
145
+ end
146
+