blinkbox-common_messaging 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2MwMWMwOWQzM2ZkMTMzN2FlNzRhMTBkZjA0MWQ0Y2JjMWFmYmY3Mw==
5
+ data.tar.gz: !binary |-
6
+ MGJkMjdlZWRlYzgwNThmYmMwYjYzOGM0MjI2MGRkNzZhMDJiZTIzNw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZDEwMTY2MThmZjdlZDBmMWY1YWQ5ODdjMjQyYjk3ZDdhOTVmYjlmODgzMTJh
10
+ OThiZWU3ZjEyMjJkZWQ1YjBjYjkzOGVmNGNhOGNlMjQxYjRiMjM1MTA3ZDdh
11
+ Nzc4ZTk3NzgxN2E2OWU2YTQ5ZDJhYzMyYmIxZjNiYTdkZTEzZjE=
12
+ data.tar.gz: !binary |-
13
+ ZGNmZTQ0YTJiN2IxZGVkMjEzY2MwMDE2ZjU4YzFlNGNmMzFjNjQ3MmNhODlk
14
+ NDVlZWQ2MjY2ZDhjZTYwMThjNzM2ZGQzZDg5OTU3NDhhZDU4OGQ0Mjc5NWY1
15
+ YjIxNTdlOGM0MmJhYzQ1MzhkYWNmOTM1OWY5ZWJiODQ3OTNmOGY=
@@ -0,0 +1,72 @@
1
+ # Change log
2
+
3
+ ## Open Source release (2015-01-28 14:11:21)
4
+
5
+ Today we have decided to publish parts of our codebase on Github under the [MIT licence](LICENCE). The licence is very permissive, but we will always appreciate hearing if and where our work is used!
6
+
7
+ ## 0.5.2 ([#9](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/9) 2014-12-22 15:03:21)
8
+
9
+ Ensure Bunny logging to Graylog
10
+
11
+ ### Improvement
12
+
13
+ - Ensure the logger is set for bunny at the low level. [Bunny Docs](http://rubybunny.info/articles/connecting.html)
14
+
15
+ ## 0.5.1 ([#8](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/8) 2014-11-25 17:44:39)
16
+
17
+ Only stringify if it can be stringified
18
+
19
+ ### Bug fix
20
+
21
+ - Mapping Updates are arrays, not objects, which fail when sent into the initialiser.
22
+
23
+ ## 0.5.0 ([#7](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/7) 2014-11-10 13:05:42)
24
+
25
+ Temporary queues
26
+
27
+ ### New feature
28
+
29
+ - Added support for temporary and exclusive queues. Needed for `common_mapping`.
30
+
31
+ ## 0.4.1 ([#5](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/5) 2014-11-06 16:31:05)
32
+
33
+ Refactor
34
+
35
+ ### Improvements
36
+
37
+ - Just shift code between two files. No code alterations whatsoever.
38
+
39
+ ## 0.4.0 ([#4](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/4) 2014-10-24 10:11:02)
40
+
41
+ Validation of messages
42
+
43
+ ### New feature
44
+
45
+ - Payloads can be validated or not, allowing 'catch all' queues.
46
+
47
+ ## 0.3.0 ([#3](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/3) 2014-10-09 10:46:10)
48
+
49
+ Prefetch
50
+
51
+ ### New feature
52
+
53
+ - Add prefetch capability
54
+
55
+ ## 0.2.0 ([#2](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/2) 2014-10-03 13:30:51)
56
+
57
+ Remote uris
58
+
59
+ ### New feature
60
+
61
+ - Any message sent with this library that contains a hash with the key `"type" => "remote"` will have its deep key placed into the `remote_uris` header for Marvin 2.0's resource fetcher to process.
62
+ - Consolidated `VERSION` retrieval.
63
+
64
+ ## 0.1.0 ([#1](https://git.mobcastdev.com/Platform/common_messaging.rb/pull/1) 2014-09-01 08:37:37)
65
+
66
+ Basic needs of the messaging library
67
+
68
+ ### New Features
69
+
70
+ - Allows blinkbox Books specific message message publishing and subscription
71
+ - Automatically validates incoming and outbound message structure against the json schema.
72
+
@@ -0,0 +1 @@
1
+ # Blinkbox::CommonMessaging
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.2
@@ -0,0 +1,239 @@
1
+ require "blinkbox/common_messaging/version"
2
+ require "bunny"
3
+ require "uri"
4
+ require "active_support/core_ext/hash/keys"
5
+ require "active_support/core_ext/hash/deep_merge"
6
+ require "active_support/core_ext/string/inflections"
7
+ require "ruby_units"
8
+ require "forwardable"
9
+ require "json-schema"
10
+ require "securerandom"
11
+ require "logger"
12
+ require "blinkbox/common_messaging/header_detectors"
13
+ require "blinkbox/common_messaging/queue"
14
+ require "blinkbox/common_messaging/exchange"
15
+
16
+ module Blinkbox
17
+ # A group of methods and classes which enable the delivery of messages through the
18
+ # blinkbox Books ecosystem via AMQP.
19
+ #
20
+ # `CommonMessaging.configure!` should be used to set up connection details first, then
21
+ # every subsequent call to `CommonMessaging::Queue.new` will create a `Bunny::Queue` object
22
+ # using the connection details that were present at the time.
23
+ module CommonMessaging
24
+ # The default RabbitMQ connection details, in the format that Bunny needs them.
25
+ DEFAULT_CONFIG = {
26
+ bunny: {
27
+ host: "localhost",
28
+ port: 5672,
29
+ user: "guest",
30
+ pass: "guest",
31
+ vhost: "/",
32
+ log_level: Logger::WARN,
33
+ automatically_recover: true,
34
+ threaded: true,
35
+ continuation_timeout: 4000
36
+ },
37
+ retry_interval: {
38
+ initial: Unit("5 seconds"),
39
+ max: Unit("5 seconds")
40
+ },
41
+ logger: Logger.new(nil)
42
+ }
43
+
44
+ # This method only stores connection details for calls to `CommonMessaging::Queue.new`.
45
+ # Any queues already created will not be affected by subsequent calls to this method.
46
+ #
47
+ # This method converts the given options from the blinkbox Books common config format
48
+ # to the format required for Bunny so that calls like the following are possible:
49
+ #
50
+ # @example Using with CommonConfig
51
+ # require "blinkbox/common_config"
52
+ # require "blinkbox/common_messaging"
53
+ #
54
+ # config = Blinkbox::CommonConfig.new
55
+ # Blinkbox::CommonMessaging.configure!(config.tree(:rabbitmq))
56
+ #
57
+ # @param [Hash] config The configuration options needed for an MQ connection.
58
+ # @option config [String] :url The URL to the RabbitMQ server, eg. amqp://user:pass@host.name:1234/virtual_host
59
+ # @option config [Unit] :initialRetryInterval The interval at which re-connection attempts should be made when a RabbitMQ failure first occurs.
60
+ # @option config [Unit] :maxRetryInterval The maximum interval at which RabbitMQ reconnection attempts should back off to.
61
+ # @param [#debug, #info, #warn, #error, #fatal] logger The logger instance which should be used by Bunny
62
+ def self.configure!(config, logger = nil)
63
+ @@config = DEFAULT_CONFIG
64
+
65
+ unless config[:url].nil?
66
+ uri = URI.parse(config[:url])
67
+ @@config.deep_merge!(
68
+ bunny: {
69
+ host: uri.host,
70
+ port: uri.port,
71
+ user: uri.user,
72
+ pass: uri.password,
73
+ vhost: uri.path
74
+ }
75
+ )
76
+ end
77
+
78
+ %i{initialRetryInterval maxRetryInterval}.each do |unit_key|
79
+ if config[unit_key]
80
+ config[unit_key] = Unit(config[unit_key]) unless config[unit_key].is_a?(Unit)
81
+
82
+ @@config.deep_merge!(
83
+ retry_interval: {
84
+ unit_key.to_s.sub('RetryInterval', '').to_sym => config[unit_key]
85
+ }
86
+ )
87
+ end
88
+ end
89
+
90
+ self.logger = logger unless logger.nil?
91
+ end
92
+
93
+ # Returns the current config being used (as used by Bunny)
94
+ #
95
+ # @return [Hash]
96
+ def self.config
97
+ @@config rescue DEFAULT_CONFIG
98
+ end
99
+
100
+ # Sets the logger delivered to Bunny when new connections are made
101
+ #
102
+ # @param [] logger The object to which log messages should be sent.
103
+ def self.logger=(logger)
104
+ %i{debug info warn error fatal level= level}.each do |m|
105
+ raise ArgumentError, "The logger did not respond to '#{m}'" unless logger.respond_to?(m)
106
+ end
107
+ @@config[:logger] = logger
108
+ @@config[:bunny][:logger] = logger
109
+ end
110
+
111
+ # Returns (and starts if necessary) the connection to the RabbitMQ server as specified by the current
112
+ # config. Will keep only one connection per configuration at any time and will return or create a new connection
113
+ # as necessary. Channels are created with publisher confirmations.
114
+ #
115
+ # Application code should not need to use this method.
116
+ #
117
+ # @return [Bunny::Session]
118
+ def self.connection
119
+ @@connections ||= {}
120
+ @@connections[config] ||= Bunny.new(config[:bunny])
121
+ @@connections[config].start
122
+ @@connections[config]
123
+ end
124
+
125
+ # Blocks until all the open connections have been closed, calling the block with any message_ids which haven't been delivered
126
+ #
127
+ # @param [Boolean] block_until_confirms Force the method to block until all messages have been acked or nacked.
128
+ # @yield [message_id] Calls the given block for any message that was undeliverable (if block_until_confirms was `true`)
129
+ # @yieldparam [String] message_id The message_id of the message which could not be delivered
130
+ def self.close_connections(block_until_confirms: true)
131
+ @@connections.each do |k, c|
132
+ if block_until_confirms && !c.wait_for_confirms
133
+ c.nacked_set.each do |message_id|
134
+ yield message_id if block_given?
135
+ end
136
+ end
137
+ c.close
138
+ end
139
+ end
140
+
141
+ module JsonSchemaPowered
142
+ extend Forwardable
143
+ def_delegators :@data, :responds_to?, :to_json, :[]
144
+
145
+ def method_missing(m, *args, &block)
146
+ @data.send(m, *args, &block)
147
+ end
148
+
149
+ def to_hash
150
+ @data
151
+ end
152
+
153
+ def to_s
154
+ @data.to_json
155
+ end
156
+
157
+ def ==(other)
158
+ self.to_hash == other.to_hash
159
+ rescue
160
+ # Any errors would be because the other isn't a hash, so the answer must be false
161
+ false
162
+ end
163
+
164
+ def inspect
165
+ classification_string = @data["classification"].map do |cl|
166
+ "#{cl["realm"]}:#{cl["id"]}"
167
+ end.join(", ")
168
+ "<#{self.class.name.split("::").last}: #{classification_string}>"
169
+ rescue
170
+ to_s
171
+ end
172
+ end
173
+
174
+ class UndeliverableMessageError < RuntimeError; end
175
+
176
+ # Generates ruby classes representing blinkbox Books messages from the schema files at the
177
+ # given path.
178
+ #
179
+ # @example Initialising CommonMessaging for sending
180
+ # Blinkbox::CommonMessaging.init_from_schema_at("ingestion.book.metatdata.v2.schema.json")
181
+ # msg = Blinkbox::CommonMessaging::IngestionBookMetadataV2.new(title: "A title")
182
+ # exchange.publish(msg)
183
+ #
184
+ # @example Using the root path
185
+ # Blinkbox::CommonMessaging.init_from_schema_at("./schema/ingestion/book/metatdata/v2.schema.json")
186
+ # # => [Blinkbox::CommonMessaging::SchemaIngestionBookMetadataV2]
187
+ #
188
+ # Blinkbox::CommonMessaging.init_from_schema_at("./schema/ingestion/book/metatdata/v2.schema.json", "./schema")
189
+ # # => [Blinkbox::CommonMessaging::IngestionBookMetadataV2]
190
+ #
191
+ # @param [String] path The path to a (or a folder of) json-schema file(s) in the blinkbox Books format.
192
+ # @param [String] root The root path from which namespaces will be calculated.
193
+ # @return Array of class names generated
194
+ def self.init_from_schema_at(path, root = path)
195
+ fail "The path #{path} does not exist" unless File.exist?(path)
196
+ return Dir[File.join(path, "**/*.schema.json")].map { |file| init_from_schema_at(file, root) }.flatten if File.directory?(path)
197
+
198
+ root = File.dirname(root) if root =~ /\.schema\.json$/
199
+ schema_name = path.sub(%r{^(?:\./)?#{root}/?(.+)\.schema\.json$}, "\\1").tr("/",".")
200
+ class_name = class_name_from_schema_name(schema_name)
201
+
202
+ # We will re-declare these classes if required, rather than raise an error.
203
+ remove_const(class_name) if constants.include?(class_name.to_sym)
204
+
205
+ const_set(class_name, Class.new {
206
+ include JsonSchemaPowered
207
+
208
+ def initialize(data = {})
209
+ @data = data
210
+ @data = @data.stringify_keys if data.respond_to?(:stringify_keys)
211
+ JSON::Validator.validate!(self.class.const_get("SCHEMA_FILE"), @data, insert_defaults: true)
212
+ end
213
+
214
+ def content_type
215
+ self.class.const_get("CONTENT_TYPE")
216
+ end
217
+ })
218
+
219
+ klass = const_get(class_name)
220
+ klass.const_set('CONTENT_TYPE', "application/vnd.blinkbox.books.#{schema_name}+json")
221
+ klass.const_set('SCHEMA_FILE', path)
222
+ klass
223
+ end
224
+
225
+ def self.class_from_content_type(content_type)
226
+ fail "No content type was given" if content_type.nil? || content_type.empty?
227
+ begin
228
+ schema_name = content_type.sub(%r{^application/vnd\.blinkbox\.books\.(.+)\+json$}, '\1')
229
+ const_get(class_name_from_schema_name(schema_name))
230
+ rescue
231
+ raise "The schema for the #{content_type} content type has not been loaded"
232
+ end
233
+ end
234
+
235
+ def self.class_name_from_schema_name(schema_name)
236
+ schema_name.tr("./", "_").camelcase
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,84 @@
1
+ module Blinkbox
2
+ module CommonMessaging
3
+ class Exchange
4
+ extend Forwardable
5
+ def_delegators :@exchange, :on_return
6
+
7
+ # A wrapped class for Bunny::Exchange. Wrapped so we can take care of message validation and header
8
+ # conventions in the blinkbox Books format.
9
+ #
10
+ # @param [String] exchange_name The name of the Exchange to connect to.
11
+ # @param [String] facility The name of the app or service (we've adopted the GELF naming term across ruby)
12
+ # @param [String] facility_version The version of the app or service which sent the message.
13
+ # @raise [Bunny::NotFound] If the exchange does not exist.
14
+ def initialize(exchange_name, facility: File.basename($0, '.rb'), facility_version: "0.0.0-unknown")
15
+ @app_id = "#{facility}:v#{facility_version}"
16
+ connection = CommonMessaging.connection
17
+ channel = connection.create_channel
18
+ channel.confirm_select
19
+ @exchange = channel.headers(
20
+ exchange_name,
21
+ durable: true,
22
+ auto_delete: false,
23
+ passive: true
24
+ )
25
+ end
26
+
27
+ # Publishes a message to the exchange with blinkbox Books default message headers and properties.
28
+ #
29
+ # Worth noting that because of a quirk of the RabbitMQ Headers Exchange you cannot route on properties
30
+ # so, in order to facilitate routing on content-type, that key is written to the headers by default as
31
+ # well as to the properties.
32
+ #
33
+ # @param [Blinkbox::CommonMessaging::JsonSchemaPowered, String] data The information which will be sent as the payload of the message. An instance of any class generated by Blinkbox::CommonMessaging.init_from_schema_at while :validate is true, or a String if false.
34
+ # @param [Hash] headers A hash of string keys and string values which will be sent as headers with the message. Used for matching.
35
+ # @param [Array<String>] message_id_chain Optional. The message_id_chain of the message which was received in order to prompt this one.
36
+ # @param [Boolean] confirm Will block this method until the MQ server has confirmed the message has been persisted and routed.
37
+ # @param [Boolean] validate if false will relax the constraint that the inbound data must be a JsonSchemaPowered object.
38
+ # @return [String] The correlation_id of the message which was delivered.
39
+ def publish(data, headers: {}, message_id_chain: [], confirm: true, validate: true)
40
+ raise ArgumentError, "All published messages must be validated. Please see Blinkbox::CommonMessaging.init_from_schema_at for details." if validate && !data.class.included_modules.include?(JsonSchemaPowered)
41
+ raise ArgumentError, "message_id_chain must be an array of strings" unless message_id_chain.is_a?(Array)
42
+
43
+ message_id = generate_message_id
44
+ new_message_id_chain = message_id_chain.dup << message_id
45
+ correlation_id = new_message_id_chain.first
46
+
47
+ headers = headers.merge!("message_id_chain" => new_message_id_chain)
48
+ options = {}
49
+
50
+ if data.respond_to?(:content_type)
51
+ hd = Blinkbox::CommonMessaging::HeaderDetectors.new(data)
52
+ headers = hd.modified_headers(headers)
53
+ # We have to do both of these because of RabbitMQ's weird header exchange protocol
54
+ headers["content-type"] = data.content_type
55
+ options[:content_type] = data.content_type
56
+ data = data.to_json
57
+ end
58
+
59
+ options.merge!(
60
+ persistent: true,
61
+ correlation_id: correlation_id,
62
+ message_id: message_id,
63
+ app_id: @app_id,
64
+ timestamp: Time.now.to_i,
65
+ headers: headers
66
+ )
67
+ @exchange.publish(data, options)
68
+
69
+ if confirm && !@exchange.channel.wait_for_confirms
70
+ message_id = @exchange.channel.nacked_set.first
71
+ raise UndeliverableMessageError, "Message #{message_id} was returned as undeliverable by RabbitMQ."
72
+ end
73
+
74
+ message_id
75
+ end
76
+
77
+ private
78
+
79
+ def generate_message_id
80
+ SecureRandom.hex(8) # 8 generates a 16 byte string
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,20 @@
1
+ class Blinkbox::CommonMessaging::HeaderDetectors
2
+ @@header_detectors = []
3
+
4
+ def initialize(obj)
5
+ @obj = obj
6
+ end
7
+
8
+ def modified_headers(original_headers = {})
9
+ @@header_detectors.each do |m|
10
+ original_headers = send(m, original_headers)
11
+ end
12
+ original_headers
13
+ end
14
+
15
+ def self.register(method_name)
16
+ @@header_detectors << method_name
17
+ end
18
+ end
19
+
20
+ Dir.glob(File.join(__dir__, "header_detectors/*.rb")) { |hd| require hd }
@@ -0,0 +1,41 @@
1
+ class Blinkbox::CommonMessaging::HeaderDetectors
2
+ def detect_remote_uris(original_headers)
3
+ hash = @obj.to_hash.extend(ExtraHashMethods)
4
+ deep_keys = hash.deep_key_select do |h|
5
+ h["type"] == "remote"
6
+ end
7
+ if !deep_keys.empty?
8
+ original_headers.merge!(
9
+ "has_remote_uris" => true,
10
+ "remote_uris" => deep_keys
11
+ )
12
+ end
13
+ original_headers
14
+ end
15
+
16
+ register :detect_remote_uris
17
+ end
18
+
19
+ module ExtraHashMethods
20
+ def deep_key_select(parent_key: "", &block)
21
+ keys = []
22
+ keys.push parent_key if block.call(self)
23
+ self.each do |k, v|
24
+ case v
25
+ when Hash
26
+ v.extend(ExtraHashMethods).deep_key_select(parent_key: k, &block).each do |hit|
27
+ keys.push [parent_key, hit].join(".").sub(/^\./,"")
28
+ end
29
+ when Array
30
+ v.each_with_index do |item, i|
31
+ if item.is_a? Hash
32
+ item.extend(ExtraHashMethods).deep_key_select(parent_key: "#{k}[#{i}]", &block).each do |hit|
33
+ keys.push [parent_key, hit].join(".").sub(/^\./,"")
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ keys
40
+ end
41
+ end
@@ -0,0 +1,149 @@
1
+ require "securerandom"
2
+
3
+ module Blinkbox
4
+ module CommonMessaging
5
+ # A proxy class for generating queues and binding them to exchanges using Bunny. In the
6
+ # format expected from blinkbox Books services.
7
+ class Queue
8
+ extend Forwardable
9
+ def_delegators :@queue, :status
10
+
11
+ # Create a queue object for subscribing to messages with.
12
+ #
13
+ # NB. There is no way to know what bindings have already been made for a queue, so all code
14
+ # subscribing to a queue should cope with receiving messages it's not expecting.
15
+ #
16
+ # @param [String] queue_name The name of the queue which should be used and (if necessary) created.
17
+ # @param [String] exchange The name of the Exchange to bind to. The default value should be avoided for production uses.
18
+ # @param [String, nil] dlx The name of the Dead Letter Exchange to send nacked messages to.
19
+ # @param [Array,Hash] bindings An array of hashes, each on detailing the parameters for a new binding.
20
+ # @param [Integer] prefetch The number of messages to collect at a time when subscribing.
21
+ # @raise [Bunny::NotFound] If the exchange does not exist.
22
+ # @return [Bunny::Queue] A blinkbox managed Bunny Queue object
23
+ def initialize(queue_name, exchange: "amq.headers", dlx: "#{exchange}.DLX", bindings: [], prefetch: 10, exclusive: false, temporary: false)
24
+ raise ArgumentError, "Prefetch must be a positive integer" unless prefetch.is_a?(Integer) && prefetch > 0
25
+ connection = CommonMessaging.connection
26
+ @logger = CommonMessaging.config[:logger]
27
+ # We create one channel per queue because it means that any issues are isolated
28
+ # and we can start a new channel and resume efforts in a segregated manner.
29
+ @channel = connection.create_channel
30
+ @channel.prefetch(prefetch)
31
+ args = {}
32
+ args["x-dead-letter-exchange"] = dlx unless dlx.nil?
33
+ @queue = @channel.queue(
34
+ queue_name,
35
+ durable: !temporary,
36
+ auto_delete: temporary,
37
+ exclusive: exclusive,
38
+ arguments: args
39
+ )
40
+ @exchange = @channel.headers(
41
+ exchange,
42
+ durable: true,
43
+ auto_delete: false,
44
+ passive: true
45
+ )
46
+ Kernel.warn "No bindings were given, the queue is unlikely to receive any messages" if bindings.empty?
47
+ bindings.each do |binding|
48
+ @queue.bind(@exchange, arguments: binding)
49
+ end
50
+ end
51
+
52
+ # Defines a new block for handling exceptions which occur when processing an incoming message. Cases where this might occur include:
53
+ #
54
+ # * A message which doesn't have a recognised content-type (ie. one which has been 'init'ed)
55
+ # * An invalid JSON message
56
+ # * A valid JSON message which doesn't pass schema validation
57
+ #
58
+ # @example Sending excepted messages to a log, then nack them
59
+ # log = Logger.new(STDOUT)
60
+ # queue = Blinkbox::CommonMessaging::Queue.new("My.Queue")
61
+ # queue.on_exception do |e, delivery_info, metadata, payload|
62
+ # log.error e
63
+ # channel.reject(delivery_info[:delivery_tag], false)
64
+ # end
65
+ #
66
+ # @yield [exception, channel, delivery)info, metadata, payload] Yields for each exception which occurs.
67
+ # @yieldparam [Exception] exception The exception which was raised.
68
+ # @yieldparam [Bunny::Connection] exception The channel this exchnage is using (useful for nacking).
69
+ # @yieldparam [Hash] delivery_info The RabbitMQ delivery info for the message (useful for nacking).
70
+ # @yieldparam [Hash] metadata The metadata delivered from the RabbitMQ server (parameters and headers).
71
+ # @yieldparam [String] payload The message that was received
72
+ def on_exception(&block)
73
+ raise ArgumentError, "Please specify a block to call when an exception is raised" unless block_given?
74
+ @on_exception = block
75
+ end
76
+
77
+ # Emits the metadata and objectified payload for every message which appears on the queue. Any message with a content-type
78
+ # not 'init'ed will be rejected (without retry) automatically.
79
+ #
80
+ # * Returning `true` or `:ack` from the block will acknowledge and remove the message from the queue
81
+ # * Returning `false` or `:reject` from the block will send the message to the DLQ
82
+ # * Returning `:retry` will put the message back on the queue to be tried again later.
83
+ #
84
+ # @example Subscribing to messages
85
+ # queue = Blinkbox::CommonMessaging::Queue.new("catch-all", exchange_name: "Marvin", [{}])
86
+ # queue.subscribe(block:true) do |metadata, obj|
87
+ # puts "Messge received."
88
+ # puts "Headers: #{metadata[:headers].to_json}"
89
+ # puts "Body: #{obj.to_json}"
90
+ # end
91
+ #
92
+ # @param [Boolean] :block Should this method block while being executed (true, default) or spawn a new thread? (false)
93
+ # @param [Array<Blinkbox::CommonMessaging::JsonSchemaPowered>, nil] :accept List of schema types to accept (any not on the list will be rejected). `nil` will accept all message types and not validate incoming messages.
94
+ # @yield [metadata, payload_object] A block to execute for each message which is received on this queue.
95
+ # @yieldparam metadata [Hash] The properties and headers (in [:headers]) delivered with the message.
96
+ # @yieldparam payload_object [Blinkbox::CommonMessaging::JsonSchemaPowered] An object representing the validated JSON payload.
97
+ # @yieldreturn [Boolean, :ack, :reject, :retry]
98
+ def subscribe(block: true, accept: nil)
99
+ raise ArgumentError, "Please give a block to run when a message is received" unless block_given?
100
+ @queue.subscribe(
101
+ block: block,
102
+ manual_ack: true
103
+ ) do |delivery_info, metadata, payload|
104
+ begin
105
+ if accept.nil?
106
+ object = payload
107
+ else
108
+ klass = Blinkbox::CommonMessaging.class_from_content_type(metadata[:headers]['content-type'])
109
+ if accept.include?(klass)
110
+ object = klass.new(JSON.parse(payload))
111
+ else
112
+ response = :reject
113
+ end
114
+ end
115
+ response ||= yield(metadata, object)
116
+ case response
117
+ when :ack, true
118
+ @channel.ack(delivery_info[:delivery_tag])
119
+ when :reject, false
120
+ @channel.reject(delivery_info[:delivery_tag], false)
121
+ when :retry
122
+ @channel.reject(delivery_info[:delivery_tag], true)
123
+ else
124
+ fail "Unknown response from subscribe block: #{response}"
125
+ end
126
+ rescue Exception => e
127
+ (@on_exception || method(:default_on_exception)).call(e, @channel, delivery_info, metadata, payload)
128
+ end
129
+ end
130
+ end
131
+
132
+ # Purges all messages from this queue. Destroys data!
133
+ #
134
+ # @return [true] Returns true if the purge occurred correctly (or a RabbitMQ error if it couldn't)
135
+ def purge!
136
+ @queue.purge
137
+ true
138
+ end
139
+
140
+ private
141
+
142
+ # The default handler for exceptions which occur when processing a message.
143
+ def default_on_exception(exception, channel, delivery_info, metadata, payload)
144
+ @logger.error exception
145
+ channel.reject(delivery_info[:delivery_tag], false)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,9 @@
1
+ module Blinkbox
2
+ module CommonMessaging
3
+ VERSION = begin
4
+ File.read(File.join(File.dirname(__FILE__), "../../../VERSION"))
5
+ rescue Errno::ENOENT
6
+ "0.0.0-unknown"
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blinkbox-common_messaging
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - JP Hastings-Spital
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-units
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json-schema
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Simple helper for messaging around blinkbox Books
112
+ email:
113
+ - jphastings@blinkbox.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - README.md
118
+ - CHANGELOG.md
119
+ files:
120
+ - CHANGELOG.md
121
+ - README.md
122
+ - VERSION
123
+ - lib/blinkbox/common_messaging.rb
124
+ - lib/blinkbox/common_messaging/exchange.rb
125
+ - lib/blinkbox/common_messaging/header_detectors.rb
126
+ - lib/blinkbox/common_messaging/header_detectors/detect_remote_uris.rb
127
+ - lib/blinkbox/common_messaging/queue.rb
128
+ - lib/blinkbox/common_messaging/version.rb
129
+ homepage: ''
130
+ licenses: []
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.4.5
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Simple helper for messaging around blinkbox Books
152
+ test_files: []