stapfen 2.2.0-java

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.
@@ -0,0 +1,35 @@
1
+ require 'stomp'
2
+
3
+ module Stapfen
4
+ module Client
5
+ class Stomp < ::Stomp::Client
6
+
7
+ def initialize(config)
8
+ # Perform a deep-copy of the configuration since +Stomp::Client+ will
9
+ # mutate/mess up the configuration +Hash+ passed in here, see:
10
+ # <https://github.com/stompgem/stomp/issues/80>
11
+ super(Marshal.load(Marshal.dump(config)))
12
+ end
13
+
14
+ def connect(*args)
15
+ # No-op, since Stomp::Client will connect on instantiation
16
+ end
17
+
18
+ def can_unreceive?
19
+ true
20
+ end
21
+
22
+ def runloop
23
+ # Performing this join/runningloop to make sure that we don't
24
+ # experience potential deadlocks between signal handlers who might
25
+ # close the connection, and an infinite Client#join call
26
+ #
27
+ # Instead of using client#open? we use #running which will still be
28
+ # true even if the client is currently in an exponential reconnect loop
29
+ while self.running do
30
+ self.join(1)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,59 @@
1
+ module Stapfen
2
+ class Destination
3
+ attr_accessor :name, :type
4
+
5
+ def queue?
6
+ @type == :queue
7
+ end
8
+
9
+ def topic?
10
+ @type == :topic
11
+ end
12
+
13
+ def as_stomp
14
+ if queue?
15
+ return "/queue/#{@name}"
16
+ end
17
+
18
+ if topic?
19
+ return "/topic/#{@name}"
20
+ end
21
+ end
22
+
23
+ def as_jms
24
+ if queue?
25
+ return "queue://#{@name}"
26
+ end
27
+
28
+ if topic?
29
+ return "topic://#{@name}"
30
+ end
31
+ end
32
+
33
+ def jms_opts
34
+ if queue?
35
+ return {:queue_name => name}
36
+ end
37
+
38
+ if topic?
39
+ return {:topic_name => name}
40
+ end
41
+ end
42
+
43
+ def as_kafka
44
+ return name
45
+ end
46
+
47
+ # Create a {Stapfen::Destination} from the given string
48
+ #
49
+ # @param [String] name
50
+ # @return [Stapfen::Destination]
51
+ def self.from_string(name)
52
+ destination = self.new
53
+ pieces = name.split('/')
54
+ destination.type = pieces[1].to_sym
55
+ destination.name = pieces[2 .. -1].join('/')
56
+ return destination
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,48 @@
1
+
2
+ module Stapfen
3
+ # Logging module to ensure that {{Stapfen::Worker}} classes can perform
4
+ # logging if they've been configured to
5
+ module Logger
6
+ # Collection of methods to pass arguments through from the class and
7
+ # instance level to a configured logger
8
+ PROXY_METHODS = [:info, :debug, :warn, :error].freeze
9
+
10
+ module ClassMethods
11
+ PROXY_METHODS.each do |method|
12
+ define_method(method) do |*args|
13
+ proxy_log_method(method, args)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def proxy_log_method(method, arguments)
20
+ if self.logger
21
+ self.logger.call.send(method, *arguments)
22
+ return true
23
+ end
24
+ return false
25
+ end
26
+ end
27
+
28
+ def self.included(klass)
29
+ klass.extend(ClassMethods)
30
+ end
31
+
32
+ PROXY_METHODS.each do |method|
33
+ define_method(method) do |*args|
34
+ proxy_log_method(method, args)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def proxy_log_method(method, arguments)
41
+ if self.class.logger
42
+ self.class.logger.call.send(method, *arguments)
43
+ return true
44
+ end
45
+ return false
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Stapfen
2
+ class Message
3
+ attr_reader :message_id, :body, :original, :destination
4
+
5
+ def initialize(opts={})
6
+ super()
7
+ @body = opts[:body]
8
+ @destination = opts[:destination]
9
+ @message_id = opts[:message_id]
10
+ @original = opts[:original]
11
+ end
12
+
13
+ # Create an instance of {Stapfen::Message} from the passed in
14
+ # {Stomp::Message}
15
+ #
16
+ # @param [Stomp::Message] message A message created by the Stomp gem
17
+ # @return [Stapfen::Message] A Stapfen wrapper object
18
+ def self.from_stomp(message)
19
+ unless message.kind_of? Stomp::Message
20
+ raise Stapfen::InvalidMessageError, message.inspect
21
+ end
22
+
23
+ return self.new(:body => message.body,
24
+ :destination => message.headers['destination'],
25
+ :message_id => message.headers['message-id'],
26
+ :original => message)
27
+ end
28
+
29
+ # Create an instance of {Stapfen::Message} from the passed in
30
+ # +ActiveMQBytesMessage+ which a JMS consumer should receive
31
+ #
32
+ # @param [ActiveMQBytesMessage] message
33
+ # @return [Stapfen::Message] A Stapfen wrapper object
34
+ def self.from_jms(message)
35
+ unless message.kind_of? Java::JavaxJms::Message
36
+ raise Stapfen::InvalidMessageError, message.inspect
37
+ end
38
+
39
+ return self.new(:body => message.data,
40
+ :destination => message.jms_destination.getQualifiedName,
41
+ :message_id => message.jms_message_id,
42
+ :original => message)
43
+ end
44
+
45
+ # Create an instance of {Stapfen::Message} from the passed in
46
+ # +String+ which a Kafka consumer should receive
47
+ #
48
+ # @param [String] message
49
+ # @return [Stapfen::Message] A Stapfen wrapper object
50
+ def self.from_kafka(message)
51
+ unless message.kind_of? String
52
+ raise Stapfen::InvalidMessageError, message.inspect
53
+ end
54
+
55
+ return self.new(:body => message)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Stapfen
2
+ VERSION = '2.2.0'
3
+ end
@@ -0,0 +1,265 @@
1
+ require 'stomp'
2
+ require 'stapfen/logger'
3
+ require 'stapfen/destination'
4
+ require 'stapfen/message'
5
+
6
+ module Stapfen
7
+ class Worker
8
+ include Stapfen::Logger
9
+
10
+ # Class variables!
11
+ @@signals_handled = false
12
+ @@workers = []
13
+
14
+ class << self
15
+ attr_accessor :configuration, :consumers, :logger, :destructor
16
+ end
17
+
18
+ # Instantiate a new +Worker+ instance and run it
19
+ def self.run!
20
+ worker = self.new
21
+
22
+ @@workers << worker
23
+
24
+ handle_signals
25
+
26
+ worker.run
27
+ end
28
+
29
+ # Expects a block to be passed which will yield the appropriate
30
+ # configuration for the Stomp gem. Whatever the block yields will be passed
31
+ # directly into the {{Stomp::Client#new}} method
32
+ def self.configure(&block)
33
+ unless block_given?
34
+ raise Stapfen::ConfigurationError
35
+ end
36
+ @configuration = block
37
+ end
38
+
39
+ # Force the worker to use STOMP as the messaging protocol (default)
40
+ #
41
+ # @return [Boolean]
42
+ def self.use_stomp!
43
+ begin
44
+ require 'stomp'
45
+ rescue LoadError
46
+ puts "You need the `stomp` gem to be installed to use stomp!"
47
+ raise
48
+ end
49
+
50
+ @protocol = 'stomp'
51
+ return true
52
+ end
53
+
54
+ def self.stomp?
55
+ @protocol.nil? || @protocol == 'stomp'
56
+ end
57
+
58
+ # Force the worker to use JMS as the messaging protocol.
59
+ #
60
+ # *Note:* Only works under JRuby
61
+ #
62
+ # @return [Boolean]
63
+ def self.use_jms!
64
+ unless RUBY_PLATFORM == 'java'
65
+ raise Stapfen::ConfigurationError, "You cannot use JMS unless you're running under JRuby!"
66
+ end
67
+
68
+ begin
69
+ require 'java'
70
+ require 'jms'
71
+ rescue LoadError
72
+ puts "You need the `jms` gem to be installed to use JMS!"
73
+ raise
74
+ end
75
+
76
+ @protocol = 'jms'
77
+ return true
78
+ end
79
+
80
+ def self.jms?
81
+ @protocol == 'jms'
82
+ end
83
+
84
+ # Force the worker to use Kafka as the messaging protocol.
85
+ #
86
+ # *Note:* Only works under JRuby
87
+ #
88
+ # @return [Boolean]
89
+ def self.use_kafka!
90
+ unless RUBY_PLATFORM == 'java'
91
+ raise Stapfen::ConfigurationError, "You cannot use Kafka unless you're running under JRuby!"
92
+ end
93
+
94
+ begin
95
+ require 'java'
96
+ require 'hermann'
97
+ rescue LoadError
98
+ puts "You need the `hermann` gem to be installed to use Kafka!"
99
+ raise
100
+ end
101
+
102
+ @protocol = 'kafka'
103
+ return true
104
+ end
105
+
106
+ def self.kafka?
107
+ @protocol == 'kafka'
108
+ end
109
+
110
+ # Optional method, should be passed a block which will yield a {{Logger}}
111
+ # instance for the Stapfen worker to use
112
+ def self.log(&block)
113
+ @logger = block
114
+ end
115
+
116
+ # Main message consumption block
117
+ def self.consume(queue_name, headers={}, &block)
118
+ unless block_given?
119
+ raise Stapfen::ConsumeError, "Cannot consume #{queue_name} without a block!"
120
+ end
121
+ @consumers ||= []
122
+ @consumers << [queue_name, headers, block]
123
+ end
124
+
125
+ # Optional method, specifes a block to execute when the worker is shutting
126
+ # down.
127
+ def self.shutdown(&block)
128
+ @destructor = block
129
+ end
130
+
131
+ # Return all the currently running Stapfen::Worker instances in this
132
+ # process
133
+ def self.workers
134
+ @@workers
135
+ end
136
+
137
+ # Invoke +exit_cleanly+ on each of the registered Worker instances that
138
+ # this class is keeping track of
139
+ #
140
+ # @return [Boolean] Whether or not we've exited/terminated cleanly
141
+ def self.exit_cleanly
142
+ return false if workers.empty?
143
+
144
+ cleanly = true
145
+ workers.each do |w|
146
+ begin
147
+ w.exit_cleanly
148
+ rescue StandardError => ex
149
+ $stderr.write("Failure while exiting cleanly #{ex.inspect}\n#{ex.backtrace}")
150
+ cleanly = false
151
+ end
152
+ end
153
+
154
+ if RUBY_PLATFORM == 'java'
155
+ info "Telling the JVM to exit cleanly"
156
+ Java::JavaLang::System.exit(0)
157
+ end
158
+
159
+ return cleanly
160
+ end
161
+
162
+ # Utility method to set up the proper worker signal handlers
163
+ def self.handle_signals
164
+ return if @@signals_handled
165
+
166
+ Signal.trap(:INT) do
167
+ self.exit_cleanly
168
+ exit!
169
+ end
170
+
171
+ Signal.trap(:TERM) do
172
+ self.exit_cleanly
173
+ end
174
+
175
+ @@signals_handled = true
176
+ end
177
+
178
+
179
+
180
+ ############################################################################
181
+ # Instance Methods
182
+ ############################################################################
183
+
184
+ attr_accessor :client
185
+
186
+ def run
187
+ if self.class.stomp?
188
+ require 'stapfen/client/stomp'
189
+ @client = Stapfen::Client::Stomp.new(self.class.configuration.call)
190
+ elsif self.class.jms?
191
+ require 'stapfen/client/jms'
192
+ @client = Stapfen::Client::JMS.new(self.class.configuration.call)
193
+ elsif self.class.kafka?
194
+ require 'stapfen/client/kafka'
195
+ @client = Stapfen::Client::Kafka.new(self.class.configuration.call)
196
+ end
197
+
198
+ debug("Running with #{@client} inside of Thread:#{Thread.current.inspect}")
199
+
200
+ @client.connect
201
+
202
+ self.class.consumers.each do |name, headers, block|
203
+ unreceive_headers = {}
204
+ [:max_redeliveries, :dead_letter_queue].each do |sym|
205
+ unreceive_headers[sym] = headers[sym] if headers.has_key? sym
206
+ end
207
+
208
+ # We're taking each block and turning it into a method so that we can
209
+ # use the instance scope instead of the blocks originally bound scope
210
+ # which would be at a class level
211
+ method_name = name.gsub(/[.|\-]/, '_').to_sym
212
+ self.class.send(:define_method, method_name, &block)
213
+
214
+ client.subscribe(name, headers) do |m|
215
+ message = nil
216
+ if self.class.stomp?
217
+ message = Stapfen::Message.from_stomp(m)
218
+ end
219
+
220
+ if self.class.jms?
221
+ message = Stapfen::Message.from_jms(m)
222
+ end
223
+
224
+ if self.class.kafka?
225
+ message = Stapfen::Message.from_kafka(m)
226
+ end
227
+
228
+ success = self.send(method_name, message)
229
+
230
+ unless success
231
+ if client.can_unreceive? && !unreceive_headers.empty?
232
+ client.unreceive(m, unreceive_headers)
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ begin
239
+ client.runloop
240
+ warn("Exiting the runloop for #{self}")
241
+ rescue Interrupt
242
+ exit_cleanly
243
+ end
244
+ end
245
+
246
+ # Invokes the shutdown block if it has been created, and closes the
247
+ # {{Stomp::Client}} connection unless it has already been shut down
248
+ def exit_cleanly
249
+ info("#{self} exiting ")
250
+ self.class.destructor.call if self.class.destructor
251
+
252
+ info "Killing client"
253
+ begin
254
+ # Only close the client if we have one sitting around
255
+ if client
256
+ unless client.closed?
257
+ client.close
258
+ end
259
+ end
260
+ rescue StandardError => exc
261
+ error "Exception received while trying to close client! #{exc.inspect}"
262
+ end
263
+ end
264
+ end
265
+ end