batsir 0.1.0 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.travis.yml +9 -1
  2. data/CHANGES.md +54 -0
  3. data/Gemfile +7 -10
  4. data/README.md +49 -5
  5. data/Rakefile +23 -16
  6. data/batsir.gemspec +43 -15
  7. data/lib/batsir/acceptors/acceptor.rb +31 -5
  8. data/lib/batsir/acceptors/amqp_acceptor.rb +36 -8
  9. data/lib/batsir/amqp.rb +35 -7
  10. data/lib/batsir/amqp_consumer.rb +8 -0
  11. data/lib/batsir/compiler/stage_worker_compiler.rb +86 -0
  12. data/lib/batsir/config.rb +208 -24
  13. data/lib/batsir/dsl/conditional_notifier_declaration.rb +31 -0
  14. data/lib/batsir/dsl/dsl_mappings.rb +27 -2
  15. data/lib/batsir/errors.rb +18 -0
  16. data/lib/batsir/filter.rb +5 -0
  17. data/lib/batsir/log.rb +7 -0
  18. data/lib/batsir/logger.rb +37 -0
  19. data/lib/batsir/logo.rb +3 -8
  20. data/lib/batsir/notifiers/amqp_notifier.rb +14 -4
  21. data/lib/batsir/notifiers/conditional_notifier.rb +29 -0
  22. data/lib/batsir/notifiers/notifier.rb +6 -5
  23. data/lib/batsir/registry.rb +6 -2
  24. data/lib/batsir/stage.rb +9 -4
  25. data/lib/batsir/stage_worker.rb +3 -56
  26. data/lib/batsir/strategies/retry_strategy.rb +35 -0
  27. data/lib/batsir/strategies/strategy.rb +20 -0
  28. data/lib/batsir/transformers/field_transformer.rb +2 -3
  29. data/lib/batsir/transformers/json_input_transformer.rb +6 -2
  30. data/lib/batsir/transformers/json_output_transformer.rb +6 -2
  31. data/lib/batsir/transformers/transformer.rb +5 -1
  32. data/lib/batsir/version.rb +10 -0
  33. data/lib/batsir.rb +31 -13
  34. data/spec/batsir/acceptors/acceptor_spec.rb +7 -78
  35. data/spec/batsir/acceptors/amqp_acceptor_spec.rb +55 -66
  36. data/spec/batsir/acceptors/shared_examples.rb +102 -0
  37. data/spec/batsir/amqp_spec.rb +58 -0
  38. data/spec/batsir/chain_spec.rb +4 -4
  39. data/spec/batsir/config_spec.rb +97 -0
  40. data/spec/batsir/dsl/chain_mapping_spec.rb +5 -6
  41. data/spec/batsir/dsl/conditional_notifier_mapping_spec.rb +80 -0
  42. data/spec/batsir/dsl/stage_mapping_spec.rb +38 -20
  43. data/spec/batsir/filter_queue_spec.rb +9 -15
  44. data/spec/batsir/filter_spec.rb +4 -5
  45. data/spec/batsir/log_spec.rb +10 -0
  46. data/spec/batsir/logger_spec.rb +46 -0
  47. data/spec/batsir/notifiers/amqp_notifier_spec.rb +43 -22
  48. data/spec/batsir/notifiers/conditional_notifier_spec.rb +62 -0
  49. data/spec/batsir/notifiers/notifier_spec.rb +4 -66
  50. data/spec/batsir/notifiers/shared_examples.rb +100 -0
  51. data/spec/batsir/registry_spec.rb +48 -0
  52. data/spec/batsir/stage_spec.rb +91 -85
  53. data/spec/batsir/stage_worker_spec.rb +13 -13
  54. data/spec/batsir/strategies/retry_strategy_spec.rb +58 -0
  55. data/spec/batsir/strategies/strategy_spec.rb +28 -0
  56. data/spec/batsir/support/bunny_mocks.rb +78 -5
  57. data/spec/batsir/transformers/field_transformer_spec.rb +22 -22
  58. data/spec/batsir/transformers/json_input_transformer_spec.rb +8 -3
  59. data/spec/batsir/transformers/json_output_transformer_spec.rb +2 -2
  60. data/spec/batsir/transformers/transformer_spec.rb +18 -4
  61. data/spec/spec_helper.rb +6 -2
  62. metadata +78 -23
  63. data/VERSION +0 -1
data/lib/batsir/config.rb CHANGED
@@ -1,34 +1,218 @@
1
1
  module Batsir
2
2
  class Config
3
- def initialize(data={})
4
- @data = {}
5
- update!(data)
6
- end
3
+ class << self
7
4
 
8
- def update!(data)
9
- data.each do |key, value|
10
- self[key] = value
5
+ # Adapted from Merb::Config class
6
+
7
+ attr_accessor :config
8
+
9
+ def defaults
10
+ @defaults ||= {
11
+ :amqp_host => 'localhost',
12
+ :amqp_port => 5672,
13
+ :amqp_user => 'guest',
14
+ :amqp_pass => 'guest',
15
+ :amqp_vhost => '/',
16
+ :amqp_durable => true,
17
+ :ampq_pool_size => 5,
18
+ :redis_host => 'localhost',
19
+ :redis_port => 6379,
20
+ :redis_database => 0,
21
+ :redis_namespace => 'batsir',
22
+ :sidekiq_queue => 'batsir',
23
+ :log_name => 'batsir'
24
+ }
25
+ end
26
+
27
+ # Returns the current configuration or sets it up
28
+ def configuration
29
+ @config ||= setup
30
+ end
31
+
32
+ # Yields the configuration.
33
+ #
34
+ # ==== Block parameters
35
+ # c<Hash>:: The configuration parameters.
36
+ #
37
+ # ==== Examples
38
+ # Batsir::Config.use do |config|
39
+ # config[:exception_details] = false
40
+ # config[:log_stream] = STDOUT
41
+ # end
42
+ #
43
+ # ==== Returns
44
+ # nil
45
+ #
46
+ # :api: public
47
+ def use
48
+ yield configuration
49
+ nil
50
+ end
51
+
52
+ # Detects whether the provided key is in the config.
53
+ #
54
+ # ==== Parameters
55
+ # key<Object>:: The key to check.
56
+ #
57
+ # ==== Returns
58
+ # Boolean:: True if the key exists in the config.
59
+ #
60
+ # :api: public
61
+ def key?(key)
62
+ configuration.key?(key)
63
+ end
64
+
65
+ # Retrieve the value of a config entry.
66
+ #
67
+ # ==== Parameters
68
+ # key<Object>:: The key to retrieve the parameter for.
69
+ #
70
+ # ==== Returns
71
+ # Object:: The value of the configuration parameter.
72
+ #
73
+ # :api: public
74
+ def [](key)
75
+ configuration[key]
76
+ end
77
+
78
+ # Set the value of a config entry.
79
+ #
80
+ # ==== Parameters
81
+ # key<Object>:: The key to set the parameter for.
82
+ # val<Object>:: The value of the parameter.
83
+ #
84
+ # :api: public
85
+ def []=(key, val)
86
+ configuration[key] = val
87
+ end
88
+
89
+ # Remove the value of a config entry.
90
+ #
91
+ # ==== Parameters
92
+ # key<Object>:: The key of the parameter to delete.
93
+ #
94
+ # ==== Returns
95
+ # Object:: The value of the removed entry.
96
+ #
97
+ # :api: public
98
+ def delete(key)
99
+ configuration.delete(key)
11
100
  end
12
- end
13
101
 
14
- def [](key)
15
- @data[key.to_sym]
16
- end
102
+ # Resets the configuration to its default state
103
+ #
104
+ def reset
105
+ setup
106
+ end
107
+
108
+ # Retrieve the value of a config entry, returning the provided default if the key is not present
109
+ #
110
+ # ==== Parameters
111
+ # key<Object>:: The key to retrieve the parameter for.
112
+ # default<Object>::
113
+ # The default value to return if the parameter is not set.
114
+ #
115
+ # ==== Returns
116
+ # Object:: The value of the configuration parameter or the default.
117
+ #
118
+ # :api: public
119
+ def fetch(key, default = nil)
120
+ configuration.fetch(key, default)
121
+ end
122
+
123
+ # Returns the configuration as a hash.
124
+ #
125
+ # ==== Returns
126
+ # Hash:: The config as a hash.
127
+ #
128
+ # :api: public
129
+ def to_hash
130
+ configuration
131
+ end
132
+
133
+ # Sets up the configuration by storing the given settings.
134
+ #
135
+ # ==== Parameters
136
+ # settings<Hash>::
137
+ # Configuration settings to use. These are merged with the defaults.
138
+ #
139
+ # ==== Returns
140
+ # The configuration as a hash.
141
+ #
142
+ # :api: private
143
+ def setup(settings = {})
144
+ @config = defaults.merge(settings)
145
+ end
17
146
 
18
- def []=(key, value)
19
- if value.class == Hash
20
- @data[key.to_sym] = Config.new(value)
21
- else
22
- @data[key.to_sym] = value
147
+ # Set configuration parameters from a code block, where each method
148
+ # evaluates to a config parameter.
149
+ #
150
+ # ==== Parameters
151
+ # &block:: Configuration parameter block.
152
+ #
153
+ # ==== Examples
154
+ # # Set environment and log level.
155
+ # Batsir::Config.configure do
156
+ # environment "development"
157
+ # log_level "debug"
158
+ # end
159
+ #
160
+ # ==== Returns
161
+ # nil
162
+ #
163
+ # :api: public
164
+ def configure(&block)
165
+ ConfigBlock.new(self, &block) if block_given?
166
+ nil
23
167
  end
24
- end
25
168
 
26
- def method_missing(sym, *args)
27
- if sym.to_s =~ /(.+)=$/
28
- self[$1] = args.first
29
- else
30
- self[sym]
169
+ # Allows retrieval of single key config values via Batsir::Config.<key>
170
+ # Allows single key assignment via Merb.config.<key> = ...
171
+ #
172
+ # ==== Parameters
173
+ # method<~to_s>:: Method name as hash key value.
174
+ # *args:: Value to set the configuration parameter to.
175
+ #
176
+ # ==== Returns
177
+ # The value of the entry fetched or assigned to.
178
+ #
179
+ # :api: public
180
+ def method_missing(method, *args)
181
+ if method.to_s[-1,1] == '='
182
+ self[method.to_s.tr('=','').to_sym] = args.first
183
+ else
184
+ self[method]
185
+ end
31
186
  end
32
- end
33
- end
187
+
188
+ end # class << self
189
+
190
+ class ConfigBlock
191
+
192
+ # Evaluates the provided block, where any call to a method causes
193
+ # #[]= to be called on klass with the method name as the key and the arguments
194
+ # as the value.
195
+ #
196
+ # ==== Parameters
197
+ # klass<Object~[]=>:: The object on which to assign values.
198
+ # &block:: The block which specifies the config values to set.
199
+ #
200
+ # ==== Returns
201
+ # nil
202
+ #
203
+ # :api: private
204
+ def initialize(klass, &block)
205
+ @klass = klass
206
+ instance_eval(&block)
207
+ end
208
+
209
+ # Assign args as the value of the entry keyed by method.
210
+ #
211
+ # :api: private
212
+ def method_missing(method, *args)
213
+ @klass[method] = args.first
214
+ end
215
+
216
+ end # ConfigBlock
217
+ end # Config
34
218
  end
@@ -0,0 +1,31 @@
1
+ module Batsir
2
+ module DSL
3
+ class ConditionalNotifierDeclaration
4
+ attr_reader :notifier_declarations
5
+
6
+ NotifierConditionDeclaration = Struct.new(:condition, :notifier, :options)
7
+
8
+ def initialize
9
+ @notifier_declarations = []
10
+ end
11
+
12
+ def add_conditional(condition, notifier_class, options)
13
+ @notifier_declarations << NotifierConditionDeclaration.new(condition, notifier_class, options)
14
+ end
15
+
16
+ def compile(output, stage_worker)
17
+ output << <<-EOF
18
+ conditional_notifier = Batsir::Notifiers::ConditionalNotifier.new
19
+ EOF
20
+ notifier_declarations.each do |notifier_declaration|
21
+ output << <<-EOF
22
+ condition = ->(message){#{notifier_declaration.condition}}
23
+ conditional_notifier.add_notifier condition, #{notifier_declaration.notifier}.new(#{notifier_declaration.options.to_s})
24
+ EOF
25
+ end
26
+ stage_worker.add_transformers_to_notifier("conditional_notifier", output)
27
+ stage_worker.add_notifier( "conditional_notifier", output)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -32,8 +32,8 @@ module Batsir
32
32
  @stage
33
33
  end
34
34
 
35
- def filter(operation)
36
- @stage.add_filter(operation)
35
+ def filter(filter_class, options = {})
36
+ @stage.add_filter(filter_class, options)
37
37
  end
38
38
 
39
39
  def inbound(&block)
@@ -43,6 +43,7 @@ module Batsir
43
43
  def outbound(&block)
44
44
  ::Blockenspiel.invoke(block, Batsir::DSL::OutboundMapping.new(@stage))
45
45
  end
46
+
46
47
  end
47
48
 
48
49
  class InboundMapping < ::Blockenspiel::Base
@@ -78,6 +79,14 @@ module Batsir
78
79
  ::Blockenspiel.invoke(block, Batsir::DSL::OutboundTransformerMapping.new(@stage))
79
80
  end
80
81
 
82
+ def conditional(&block)
83
+ new_block = ::Proc.new do
84
+ conditional &block
85
+ end
86
+ conditional = ::Blockenspiel.invoke(new_block, Batsir::DSL::ConditionalNotifierMapping.new)
87
+ @stage.add_conditional_notifier(conditional)
88
+ end
89
+
81
90
  def notifier(notifier_class, options = {})
82
91
  @stage.add_notifier(notifier_class, options)
83
92
  end
@@ -92,5 +101,21 @@ module Batsir
92
101
  @stage.add_notifier_transformer(transformer, options)
93
102
  end
94
103
  end
104
+
105
+ class ConditionalNotifierMapping < ::Blockenspiel::Base
106
+ def initialize
107
+ @notifier = nil
108
+ end
109
+
110
+ def conditional(&block)
111
+ @notifier = Batsir::DSL::ConditionalNotifierDeclaration.new
112
+ ::Blockenspiel.invoke(block, self)
113
+ @notifier
114
+ end
115
+
116
+ def notify_if(condition, notifier, options = {})
117
+ @notifier.add_conditional(condition, notifier, options)
118
+ end
119
+ end
95
120
  end
96
121
  end
@@ -0,0 +1,18 @@
1
+ module Batsir
2
+ module Errors
3
+
4
+ class BatsirError < RuntimeError; end
5
+ class NotifierError < BatsirError; end
6
+ class TransformError < BatsirError; end
7
+ class StrategyError < BatsirError; end
8
+
9
+ class ExecuteMethodNotImplementedError < BatsirError; end
10
+
11
+ class NotifierConnectionError < NotifierError; end
12
+
13
+ class JSONInputTransformError < TransformError; end
14
+ class JSONOutputTransformError < TransformError; end
15
+
16
+ class RetryStrategyFailed < StrategyError; end
17
+ end
18
+ end
data/lib/batsir/filter.rb CHANGED
@@ -6,7 +6,12 @@ module Batsir
6
6
  end
7
7
  end
8
8
 
9
+ def filter(message)
10
+ execute(message)
11
+ end
12
+
9
13
  def execute(message)
14
+ raise NotImplementedError.new
10
15
  end
11
16
  end
12
17
  end
data/lib/batsir/log.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Batsir
2
+ module Log
3
+ def log
4
+ Batsir::Logger
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ require 'log4r'
2
+
3
+ module Batsir
4
+ module Logger
5
+ DEBUG = 1
6
+ INFO = 2
7
+ WARN = 3
8
+ ERROR = 4
9
+ FATAL = 5
10
+
11
+ class << self
12
+
13
+ DEFAULT_OUTPUT = 'stdout'
14
+
15
+ def log
16
+ @logger ||= setup
17
+ end
18
+
19
+ def setup
20
+ logger = Log4r::Logger.new(Batsir::Config.fetch(:log_name, "batsir"))
21
+ logger.level = Batsir::Config.fetch(:log_level, WARN)
22
+ logger.outputters = Batsir::Config.fetch(:log_outputter, DEFAULT_OUTPUT)
23
+ logger
24
+ end
25
+
26
+ def reset
27
+ @logger = nil
28
+ end
29
+
30
+ # makes this respond like a Log4r::Logger
31
+ def method_missing(sym, *args, &block)
32
+ log.send sym, *args, &block
33
+ end
34
+
35
+ end
36
+ end
37
+ end
data/lib/batsir/logo.rb CHANGED
@@ -2,10 +2,6 @@ module Batsir
2
2
  def self.logo
3
3
  <<EOF
4
4
 
5
-
6
-
7
-
8
-
9
5
  E ,.
10
6
  ,,,agB8@f ,8v ,8L J88&gg,,,
11
7
  .,a88888888@^ |8&,,,,,,88k ,88888888&g,,
@@ -26,10 +22,9 @@ module Batsir
26
22
  `?9@888&, j88f ,d88$@9l'
27
23
  `|?9t |f `9T"`
28
24
 
29
-
30
-
31
-
32
-
25
+ ______ _______ _______ _______ _____ ______
26
+ |_____] |_____| | |______ | |_____/
27
+ |_____] | | | ______| __|__ | \\_
33
28
 
34
29
  EOF
35
30
  end
@@ -5,11 +5,21 @@ module Batsir
5
5
  class AMQPNotifier < Notifier
6
6
  include Batsir::AMQP
7
7
 
8
+ attr_reader :error_strategy
9
+
10
+ def initialize(options = {}, error_strategy = Batsir::Strategies::RetryStrategy)
11
+ super(options)
12
+ @error_strategy = error_strategy.new(self)
13
+ @bunny = Bunny.new(bunny_options).start
14
+ @x = @bunny.exchange( exchange )
15
+ end
16
+
8
17
  def execute(message)
9
- Bunny.run( bunny_options )do |bunny|
10
- exc = bunny.exchange( exchange )
11
- exc.publish( message, :key => queue )
12
- end
18
+ @x.publish(message, :routing_key => queue)
19
+ end
20
+
21
+ def handle_error(message, error)
22
+ @error_strategy.execute(message, error)
13
23
  end
14
24
  end
15
25
  end
@@ -0,0 +1,29 @@
1
+ module Batsir
2
+ module Notifiers
3
+ class ConditionalNotifier < Notifier
4
+ attr_accessor :notifiers
5
+
6
+ NotifierCondition = Struct.new(:condition, :notifier, :options)
7
+
8
+ def initialize(options = {})
9
+ super
10
+ @notifiers = []
11
+ end
12
+
13
+ def add_notifier( condition, notifier_class, options = {})
14
+ self.notifiers << NotifierCondition.new(condition, notifier_class, options)
15
+ end
16
+
17
+ def execute(message)
18
+ self.notifiers.each do |notifier_condition|
19
+ if notifier_condition.condition.call(message)
20
+ notifier = notifier_condition.notifier
21
+ options = notifier_condition.options
22
+ notifier.notify(message)
23
+ end
24
+ end
25
+ message
26
+ end
27
+ end
28
+ end
29
+ end
@@ -21,11 +21,8 @@ module Batsir
21
21
  end
22
22
 
23
23
  def notify(message)
24
- execute(transform(message))
25
- end
26
-
27
- def execute(message)
28
-
24
+ execute(transform(message.clone))
25
+ message
29
26
  end
30
27
 
31
28
  def transform(message)
@@ -34,6 +31,10 @@ module Batsir
34
31
  end
35
32
  message
36
33
  end
34
+
35
+ def execute(message)
36
+ raise NotImplementedError.new
37
+ end
37
38
  end
38
39
  end
39
40
  end
@@ -5,11 +5,15 @@ module Batsir
5
5
  end
6
6
 
7
7
  def self.registry
8
- @registry ||= {}
8
+ @registry || reset
9
9
  end
10
10
 
11
11
  def self.get(name)
12
- registry.fetch(name)
12
+ registry.fetch(name, nil)
13
+ end
14
+
15
+ def self.reset
16
+ @registry = {}
13
17
  end
14
18
  end
15
19
  end
data/lib/batsir/stage.rb CHANGED
@@ -10,6 +10,7 @@ module Batsir
10
10
  attr_accessor :cancellators
11
11
  attr_reader :filter_declarations
12
12
  attr_reader :notifiers
13
+ attr_reader :conditional_notifiers
13
14
  attr_reader :acceptors
14
15
  attr_reader :running_acceptors
15
16
  attr_reader :notifier_transformers
@@ -25,6 +26,7 @@ module Batsir
25
26
  @acceptors = {}
26
27
  @filter_declarations = []
27
28
  @notifiers = {}
29
+ @conditional_notifiers = []
28
30
  @notifier_transformers = []
29
31
  @built = false
30
32
  end
@@ -50,6 +52,10 @@ module Batsir
50
52
  @filter_declarations.map{ |filter_declaration| filter_declaration.filter }
51
53
  end
52
54
 
55
+ def add_conditional_notifier(notifier_declaration)
56
+ @conditional_notifiers << notifier_declaration
57
+ end
58
+
53
59
  def add_notifier(notifier, options = {})
54
60
  @notifiers[notifier] ||= Set.new
55
61
  @notifiers[notifier] << options
@@ -72,10 +78,9 @@ module Batsir
72
78
  def start
73
79
  acceptors.each do |acceptor_class, options|
74
80
  options.each do |acceptor_options|
75
- cancellator_reader, cancellator_writer = ::IO.pipe
76
- acceptor_options.merge!(:stage_name => self.name, :cancellator => cancellator_reader)
77
- @cancellators << cancellator_writer
81
+ acceptor_options.merge!(:stage_name => self.name)
78
82
  acceptor = acceptor_class.new(acceptor_options)
83
+
79
84
  if acceptor_transformers.any?
80
85
  acceptor_transformers.each do |transformer_declaration|
81
86
  transformer = transformer_declaration.transformer.new(transformer_declaration.options)
@@ -85,7 +90,7 @@ module Batsir
85
90
  acceptor.add_transformer(Batsir::Transformers::JSONInputTransformer.new)
86
91
  end
87
92
  @running_acceptors << acceptor
88
- acceptor.start!
93
+ acceptor.async.start
89
94
  end
90
95
  end
91
96
  true
@@ -14,9 +14,10 @@ module Batsir
14
14
  end
15
15
 
16
16
  def execute(message)
17
+ return false if message.nil?
17
18
  return false unless @filter_queue
18
19
  @filter_queue.filters.each do |filter|
19
- message = filter.execute(message)
20
+ message = filter.filter(message)
20
21
  return false if message.nil?
21
22
  end
22
23
  @filter_queue.notifiers.each do |notifier|
@@ -26,61 +27,7 @@ module Batsir
26
27
  end
27
28
 
28
29
  def self.compile_from(stage)
29
- code = <<-EOF
30
- class #{stage.name.capitalize.gsub(' ','')}Worker
31
- def self.stage_name
32
- "#{stage.name}"
33
- end
34
-
35
- def initialize
36
- @filter_queue = self.class.filter_queue
37
- end
38
-
39
- def self.filter_queue
40
- @filter_queue
41
- end
42
-
43
- def self.initialize_filter_queue
44
- @filter_queue = Batsir::FilterQueue.new
45
- EOF
46
-
47
- stage.filter_declarations.each do |filter_declaration|
48
- code << <<-EOF
49
- @filter_queue.add #{filter_declaration.filter.to_s}.new(#{filter_declaration.options.to_s})
50
- EOF
51
- end
52
-
53
- stage.notifiers.each do |notifier, options_set|
54
- options_set.each do |options|
55
- code << <<-EOF
56
- notifier = #{notifier.to_s}.new(#{options.to_s})
57
- EOF
58
-
59
- if stage.notifier_transformers.any?
60
- stage.notifier_transformers.each do |transformer_declaration|
61
- code << <<-EOF
62
- notifier.add_transformer #{transformer_declaration.transformer}.new(#{transformer_declaration.options.to_s})
63
- EOF
64
- end
65
- else
66
- code << <<-EOF
67
- notifier.add_transformer Batsir::Transformers::JSONOutputTransformer.new
68
- EOF
69
- end
70
- code << <<-EOF
71
- @filter_queue.add_notifier notifier
72
- EOF
73
- end
74
- end
75
-
76
- code << <<-EOF
77
- end
78
-
79
- include Sidekiq::Worker
80
- include Batsir::StageWorker
81
- end
82
- EOF
83
- code
30
+ Batsir::Compiler::StageWorkerCompiler.new(stage).compile
84
31
  end
85
32
  end
86
33
  end
@@ -0,0 +1,35 @@
1
+ module Batsir
2
+ module Strategies
3
+ class RetryStrategy < Strategy
4
+ include Batsir::Log
5
+
6
+ attr_reader :retries, :attempts
7
+
8
+ def initialize(context, retries = 3)
9
+ super(context)
10
+ @retries = retries
11
+ @attempts = {}
12
+ end
13
+
14
+ def execute(message, error)
15
+ @attempts[message] ? @attempts[message] += 1 : @attempts[message] = 0
16
+
17
+ if @attempts[message] >= @retries
18
+ error_msg = "Tried to send '#{message}' #{@attempts[message]} times and failed"
19
+ reset_attempts(message)
20
+ log.error error_msg
21
+ raise Batsir::Errors::RetryStrategyFailed.new error_msg
22
+ else
23
+ log.warn "Recovering from #{error}. Making another attempt (##{@attempts[message]+1})"
24
+ result = @context.execute(message)
25
+ reset_attempts(message)
26
+ return result
27
+ end
28
+ end
29
+
30
+ def reset_attempts(message)
31
+ @attempts.delete(message)
32
+ end
33
+ end
34
+ end
35
+ end