activemessaging 0.6.1 → 0.7.1

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.
@@ -1,41 +1,82 @@
1
1
  require 'stomp'
2
2
 
3
+ require 'activemessaging/adapters/base'
4
+
3
5
  module ActiveMessaging
4
6
  module Adapters
5
7
  module Stomp
6
-
7
- class Connection < ::Stomp::Connection
8
- include ActiveMessaging::Adapter
8
+
9
+ class Connection < ActiveMessaging::Adapters::BaseConnection
9
10
  register :stomp
10
11
 
11
- attr_accessor :reliable, :retryMax, :deadLetterQueue
12
+ attr_accessor :stomp_connection, :retryMax, :deadLetterQueue, :configuration
12
13
 
13
14
  def initialize(cfg)
14
15
  @retryMax = cfg[:retryMax] || 0
15
16
  @deadLetterQueue = cfg[:deadLetterQueue] || nil
16
-
17
+
17
18
  cfg[:login] ||= ""
18
19
  cfg[:passcode] ||= ""
19
20
  cfg[:host] ||= "localhost"
20
21
  cfg[:port] ||= "61613"
21
- cfg[:reliable] ||= TRUE
22
+ cfg[:reliable] = cfg[:reliable].nil? ? TRUE : cfg[:reliable].nil?
22
23
  cfg[:reconnectDelay] ||= 5
23
24
  cfg[:clientId] ||= nil
24
-
25
- if cfg[:clientId]
26
- super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay],cfg[:clientId])
27
- else
28
- super(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay])
29
- end
30
25
 
26
+ # hold on to the config
27
+ @configuration = cfg
28
+
29
+ # create a new stomp connection
30
+ connect_headers = {}
31
+ connect_headers['client-id'] = cfg[:clientId] if cfg[:clientId]
32
+ @stomp_connection = ::Stomp::Connection.new(cfg[:login],cfg[:passcode],cfg[:host],cfg[:port].to_i,cfg[:reliable],cfg[:reconnectDelay], connect_headers)
31
33
  end
32
-
34
+
35
+ # called to cleanly get rid of connection
36
+ def disconnect
37
+ @stomp_connection.disconnect
38
+ end
39
+
40
+ # destination_name string, headers hash
41
+ # subscribe to listen on a destination
42
+ def subscribe destination_name, message_headers={}
43
+ @stomp_connection.subscribe(destination_name, message_headers)
44
+ end
45
+
46
+ # destination_name string, headers hash
47
+ # unsubscribe to listen on a destination
48
+ def unsubscribe destination_name, message_headers={}
49
+ @stomp_connection.unsubscribe(destination_name, message_headers)
50
+ end
51
+
52
+ # destination_name string, body string, headers hash
53
+ # send a single message to a destination
54
+ def send destination_name, message_body, message_headers={}
55
+ stomp_publish(destination_name, message_body, message_headers)
56
+ end
57
+
58
+ # receive a single message from any of the subscribed destinations
59
+ # check each destination once, then sleep for poll_interval
60
+ def receive
61
+ m = @stomp_connection.receive
62
+ Message.new(m) if m
63
+ end
64
+
33
65
  def received message, headers={}
34
66
  #check to see if the ack mode for this subscription is auto or client
35
67
  # if the ack mode is client, send an ack
36
68
  if (headers[:ack] === 'client')
37
- ack_headers = message.headers.has_key?(:transaction) ? message.headers[:transaction] : {}
38
- ack message.headers['message-id'], ack_headers
69
+ ack_headers = message.headers.has_key?(:transaction) ? { :transaction=>message.headers[:transaction]} : {}
70
+ @stomp_connection.ack(message.headers['message-id'], ack_headers)
71
+ end
72
+ end
73
+
74
+ # send has been deprecated in latest stomp gem (as it should be)
75
+ def stomp_publish(destination_name="", message_body="", message_headers={})
76
+ if @stomp_connection.respond_to?(:publish)
77
+ @stomp_connection.publish(destination_name, message_body, message_headers)
78
+ else
79
+ @stomp_connection.send(destination_name, message_body, message_headers)
39
80
  end
40
81
  end
41
82
 
@@ -44,7 +85,7 @@ module ActiveMessaging
44
85
  transaction_id = "transaction-#{message.headers['message-id']}-#{retry_count}"
45
86
 
46
87
  # start a transaction, send the message back to the original destination
47
- self.begin(transaction_id)
88
+ @stomp_connection.begin(transaction_id)
48
89
  begin
49
90
 
50
91
  if @retryMax > 0
@@ -53,6 +94,8 @@ module ActiveMessaging
53
94
  retry_headers.delete('content-length')
54
95
  retry_headers.delete('content-type')
55
96
 
97
+ retry_destination = retry_headers.delete('destination')
98
+
56
99
  if retry_count < @retryMax
57
100
  # now send the message back to the destination
58
101
  # set the headers for message id, priginal message id, and retry count
@@ -65,13 +108,15 @@ module ActiveMessaging
65
108
  retry_headers['a13g-retry-count'] = retry_count + 1
66
109
 
67
110
  # send the updated message to retry in the same transaction
68
- self.send retry_headers['destination'], message.body, retry_headers
111
+ self.stomp_publish(retry_destination, message.body, retry_headers)
69
112
 
70
113
  elsif retry_count >= @retryMax && @deadLetterQueue
71
- # send the 'poison pill' message to the dead letter queue
114
+ # send the 'poison pill' message to the dead letter queue - make it persistent by default
72
115
  retry_headers['a13g-original-destination'] = retry_headers.delete('destination')
116
+ retry_headers['persistent'] = true
73
117
  retry_headers.delete('message-id')
74
- self.send @deadLetterQueue, message.body, retry_headers
118
+
119
+ self.stomp_publish(@deadLetterQueue, message.body, retry_headers)
75
120
  end
76
121
 
77
122
  end
@@ -79,14 +124,14 @@ module ActiveMessaging
79
124
  #check to see if the ack mode is client, and if it is, ack it in this transaction
80
125
  if (headers[:ack] === 'client')
81
126
  # ack the original message
82
- self.ack message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id)
127
+ @stomp_connection.ack(message.headers['message-id'], message.headers.stringify_keys.merge('transaction'=>transaction_id))
83
128
  end
84
129
 
85
130
  # now commit the transaction
86
- self.commit transaction_id
131
+ @stomp_connection.commit transaction_id
87
132
  rescue Exception=>exc
88
133
  # if there is an error, try to abort the transaction, then raise the error
89
- self.abort transaction_id
134
+ @stomp_connection.abort transaction_id
90
135
  raise exc
91
136
  end
92
137
 
@@ -94,6 +139,29 @@ module ActiveMessaging
94
139
 
95
140
  end
96
141
 
142
+ class Message < ActiveMessaging::BaseMessage
143
+
144
+ def initialize(msg)
145
+ super(msg.body, msg.headers['message-id'], msg.headers, msg.headers['destination'])
146
+ end
147
+
148
+ def matches_subscription?(subscription)
149
+ # if the subscription has been specified in the headers, rely on this
150
+ if self.headers['subscription'] && subscription.subscribe_headers['id']
151
+ self.headers['subscription'].to_s == subscription.subscribe_headers['id'].to_s
152
+
153
+ # see if the destination uses a wildcard representation
154
+ elsif subscription.destination.wildcard
155
+ self.destination.to_s =~ subscription.destination.wildcard
156
+
157
+ # no subscription id? no wildcard? use the name of the destination as a straight match
158
+ else
159
+ self.destination.to_s == subscription.destination.value.to_s
160
+ end
161
+ end
162
+
163
+ end
164
+
97
165
  end
98
166
  end
99
167
  end
@@ -1,11 +1,10 @@
1
- require 'activemessaging/adapter'
1
+ require 'activemessaging/adapters/base'
2
2
 
3
3
  module ActiveMessaging
4
4
  module Adapters
5
5
  module Test
6
6
 
7
- class Connection
8
- include ActiveMessaging::Adapter
7
+ class Connection < ActiveMessaging::Adapters::BaseConnection
9
8
  register :test
10
9
 
11
10
  attr_accessor :config, :subscriptions, :destinations, :connected, :received_messages, :unreceived_messages
@@ -42,7 +41,7 @@ module ActiveMessaging
42
41
  def send destination_name, message_body, message_headers={}
43
42
  open_destination destination_name
44
43
  destination = find_destination destination_name
45
- destination.send Message.new(message_headers, nil, message_body, nil, destination)
44
+ destination.send Message.new(message_body, nil, message_headers, destination_name)
46
45
  end
47
46
 
48
47
  def receive
@@ -52,15 +51,6 @@ module ActiveMessaging
52
51
  destination.receive unless destination.nil?
53
52
  end
54
53
 
55
- # should not be 2 defs for receive, this isn't java, ya know? -Andrew
56
- # def receive destination_name, headers={}
57
- # subscribe destination_name, headers
58
- # destination = find_destination destination_name
59
- # message = destination.receive
60
- # unsubscribe destination_name, headers
61
- # message
62
- # end
63
-
64
54
  def received message, headers={}
65
55
  @received_messages << message
66
56
  end
@@ -71,8 +61,9 @@ module ActiveMessaging
71
61
 
72
62
  #test helper methods
73
63
  def find_message destination_name, body
64
+
74
65
  all_messages.find do |m|
75
- m.headers['destination'] == destination_name && if body.is_a?(Regexp)
66
+ m.destination == destination_name && if body.is_a?(Regexp)
76
67
  m.body =~ body
77
68
  else
78
69
  m.body == body.to_s
@@ -138,18 +129,9 @@ module ActiveMessaging
138
129
  end
139
130
  end
140
131
 
141
- class Message
142
- attr_accessor :headers, :body, :command
143
-
144
- def initialize headers, id, body, response, destination, command='MESSAGE'
145
- @headers, @body, @command = headers, body, command
146
- headers['destination'] = destination.name
147
- end
148
-
149
- def to_s
150
- "<Test::Message body='#{body}' headers='#{headers.inspect}' command='#{command}' >"
151
- end
132
+ class Message < ActiveMessaging::BaseMessage
152
133
  end
134
+
153
135
  end
154
136
  end
155
137
  end
@@ -19,18 +19,16 @@
19
19
  #
20
20
  require 'wmq/wmq'
21
21
 
22
+ require 'activemessaging/adapters/base'
23
+
22
24
  module ActiveMessaging
23
25
  module Adapters
24
26
  module Adapter
25
27
 
26
28
  # Connection class needed by a13g
27
- class Connection
28
- include ActiveMessaging::Adapter
29
+ class Connection < ActiveMessaging::Adapters::BaseConnection
29
30
  register :wmq
30
31
 
31
- # Needed by a13g but never used within this adapter
32
- attr_accessor :reliable
33
-
34
32
  # Generic init method needed by a13g
35
33
  def initialize(cfg)
36
34
  # Set default values
@@ -147,22 +145,15 @@ module ActiveMessaging
147
145
 
148
146
  # Message class needed by a13g (based on the same Message class in Stomp adapter)
149
147
  # Contains a reference to the MQ message object ;-) !
150
- class Message
151
- # Accessors needed by a13g
152
- attr_accessor :headers, :body, :command, :wmq_message
148
+ class Message < ActiveMessaging::BaseMessage
149
+
150
+ attr_accessor :wmq_message
153
151
 
154
152
  def initialize(wmq_message, q_name)
153
+ super(wmq_message.data, wmq_message.descriptor[:msg_id], {'destination' => q_name}, q_name)
155
154
  @wmq_message = wmq_message
156
-
157
- # Needed by a13g
158
- @headers = {'destination' => q_name}
159
- @body = wmq_message.data
160
- @command = 'MESSAGE'
161
155
  end
162
156
 
163
- def to_s
164
- "<Adapter::Message headers=#{@headers.inspect} body='#{@body}' command='#{@command}' wmq_message=#{@wmq_message}>"
165
- end
166
157
  end
167
158
 
168
159
  private
@@ -0,0 +1,19 @@
1
+ module ActiveMessaging
2
+
3
+ class BaseMessage
4
+ attr_accessor :body, :id, :headers, :destination
5
+
6
+ def initialize(body=nil, id=nil, headers={}, destination=nil)
7
+ @body, @id, @headers, @destination = body, id, headers, destination
8
+ end
9
+
10
+ def matches_subscription?(subscription)
11
+ self.destination.to_s == subscription.destination.value.to_s
12
+ end
13
+
14
+ def to_s
15
+ "<#{self.class.name} id='#{id}' headers='#{headers.inspect}' body='#{body}' >"
16
+ end
17
+ end
18
+
19
+ end
@@ -189,13 +189,14 @@ module ActiveMessaging
189
189
 
190
190
  def prepare_application
191
191
  if defined? Rails
192
- Dispatcher.prepare_application_for_dispatch
192
+ # Dispatcher.prepare_application_for_dispatch
193
+ ActiveRecord::Base.verify_active_connections!
193
194
  end
194
195
  end
195
196
 
196
197
  def reset_application
197
198
  if defined? Rails
198
- Dispatcher.reset_application_after_dispatch
199
+ # Dispatcher.reset_application_after_dispatch
199
200
  end
200
201
  end
201
202
 
@@ -215,39 +216,33 @@ module ActiveMessaging
215
216
  end
216
217
 
217
218
  def _dispatch(message)
218
- case message.command
219
- when 'ERROR'
220
- ActiveMessaging.logger.error('Error from messaging infrastructure: ' + message.headers['message'])
221
- when 'MESSAGE'
222
- abort = false
223
- processed = false
224
-
225
- subscriptions.each do |key, subscription|
226
- if subscription.matches?(message) then
227
- processed = true
228
- routing = {
229
- :receiver=>subscription.processor_class,
230
- :destination=>subscription.destination,
231
- :direction => :incoming
232
- }
233
- begin
234
- execute_filter_chain(:incoming, message, routing) do |m|
235
- result = subscription.processor_class.new.process!(m)
236
- end
237
- rescue ActiveMessaging::AbortMessageException
238
- abort_message subscription, message
239
- abort = true
240
- return
241
- ensure
242
- acknowledge_message subscription, message unless abort
219
+ abort = false
220
+ processed = false
221
+
222
+ subscriptions.each do |key, subscription|
223
+ if message.matches_subscription?(subscription) then
224
+ processed = true
225
+ routing = {
226
+ :receiver=>subscription.processor_class,
227
+ :destination=>subscription.destination,
228
+ :direction => :incoming
229
+ }
230
+ begin
231
+ execute_filter_chain(:incoming, message, routing) do |m|
232
+ result = subscription.processor_class.new.process!(m)
243
233
  end
234
+ rescue ActiveMessaging::AbortMessageException
235
+ abort_message subscription, message
236
+ abort = true
237
+ return
238
+ ensure
239
+ acknowledge_message subscription, message unless abort
244
240
  end
245
241
  end
246
-
247
- ActiveMessaging.logger.error("No-one responded to #{message}") unless processed
248
- else
249
- ActiveMessaging.logger.error('Unknown message command: ' + message.inspect)
250
242
  end
243
+
244
+ ActiveMessaging.logger.error("No-one responded to #{message}") unless processed
245
+
251
246
  end
252
247
 
253
248
  # acknowledge_message is called when the message has been processed w/o error by at least one processor
@@ -268,7 +263,7 @@ module ActiveMessaging
268
263
 
269
264
  def destination destination_name, destination, publish_headers={}, broker='default'
270
265
  raise "You already defined #{destination_name} to #{named_destinations[destination_name].value}" if named_destinations.has_key?(destination_name)
271
- named_destinations[destination_name] = Destination.new destination_name, destination, publish_headers, broker
266
+ named_destinations[destination_name] = Destination.new(destination_name, destination, publish_headers, broker)
272
267
  end
273
268
 
274
269
  alias queue destination
@@ -385,16 +380,10 @@ module ActiveMessaging
385
380
  @destination, @processor_class, @subscribe_headers = destination, processor_class, subscribe_headers
386
381
  subscribe_headers['id'] = processor_class.name.underscore unless subscribe_headers.key? 'id'
387
382
  end
388
-
389
- def matches?(message)
390
- message.headers['destination'].to_s == @destination.value.to_s
391
- end
392
-
383
+
393
384
  def subscribe
394
385
  ActiveMessaging.logger.error "=> Subscribing to #{destination.value} (processed by #{processor_class})"
395
386
  Gateway.connection(@destination.broker_name).subscribe(@destination.value, subscribe_headers)
396
- # FIXME (uwe): Not sure why this needs to happen here
397
- @processor = @processor_class.new
398
387
  end
399
388
 
400
389
  def unsubscribe
@@ -406,15 +395,23 @@ module ActiveMessaging
406
395
  class Destination
407
396
  DEFAULT_PUBLISH_HEADERS = { :persistent=>true }
408
397
 
409
- attr_accessor :name, :value, :publish_headers, :broker_name
398
+ attr_accessor :name, :value, :publish_headers, :broker_name, :wildcard
410
399
 
411
400
  def initialize(name, value, publish_headers, broker_name)
412
401
  @name, @value, @publish_headers, @broker_name = name, value, publish_headers, broker_name
413
402
  @publish_headers.reverse_merge! DEFAULT_PUBLISH_HEADERS
403
+ @wildcard = wildcard_match_exp_for(value) if (value =~ /\*/)
414
404
  end
415
405
 
416
406
  def to_s
417
- "#{broker_name}: #{name} => '#{value}'"
407
+ "<destination: #{broker_name} :#{name}=>'#{value}'>"
408
+ end
409
+
410
+ private
411
+
412
+ def wildcard_match_exp_for(destination)
413
+ exp = destination.to_s.gsub(/[.]/, '\.').gsub(/[*]/, '[^.*]+').gsub(/([>].*$)/, '.*') + '$'
414
+ Regexp.new(exp)
418
415
  end
419
416
 
420
417
  end