activemessaging 0.6.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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