killbill 3.2.2 → 3.2.3

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -2
  3. data/Jarfile +7 -7
  4. data/README.md +16 -4
  5. data/VERSION +1 -1
  6. data/gen_config/api.conf +48 -47
  7. data/gen_config/plugin_api.conf +17 -16
  8. data/generators/active_merchant/templates/.gitignore.rb +2 -2
  9. data/generators/active_merchant/templates/lib/api.rb +8 -0
  10. data/generators/active_merchant/templates/plugin.gemspec.rb +1 -0
  11. data/generators/active_merchant/templates/spec/base_plugin_spec.rb +1 -0
  12. data/generators/active_merchant/templates/spec/integration_spec.rb +5 -1
  13. data/killbill.gemspec +1 -0
  14. data/lib/killbill/creator.rb +9 -0
  15. data/lib/killbill/currency.rb +6 -0
  16. data/lib/killbill/gen/api/account_audit_logs_for_object_type.rb +9 -7
  17. data/lib/killbill/gen/api/catalog_user_api.rb +37 -4
  18. data/lib/killbill/gen/api/payment_api.rb +104 -0
  19. data/lib/killbill/gen/api/payment_gateway_api.rb +6 -3
  20. data/lib/killbill/gen/api/plan_phase.rb +1 -8
  21. data/lib/killbill/gen/api/price_list_set.rb +9 -7
  22. data/lib/killbill/gen/api/subscription.rb +20 -1
  23. data/lib/killbill/gen/api/tenant_user_api.rb +3 -3
  24. data/lib/killbill/gen/plugin-api/currency_plugin_with_events_api.rb +207 -0
  25. data/lib/killbill/gen/plugin-api/ext_bus_event.rb +7 -1
  26. data/lib/killbill/gen/plugin-api/invoice_plugin_with_events_api.rb +103 -0
  27. data/lib/killbill/gen/plugin-api/payment_plugin_with_events_api.rb +782 -0
  28. data/lib/killbill/gen/plugin-api/require_gen.rb +3 -0
  29. data/lib/killbill/helpers/active_merchant/configuration.rb +116 -46
  30. data/lib/killbill/helpers/active_merchant/killbill_spec_helper.rb +49 -32
  31. data/lib/killbill/helpers/active_merchant/payment_plugin.rb +18 -6
  32. data/lib/killbill/helpers/active_merchant/private_payment_plugin.rb +3 -3
  33. data/lib/killbill/helpers/active_merchant/utils.rb +37 -70
  34. data/lib/killbill/invoice.rb +6 -0
  35. data/lib/killbill/payment.rb +6 -0
  36. data/lib/killbill/plugin.rb +1 -1
  37. data/lib/killbill/rake_task.rb +1 -1
  38. data/spec/killbill/helpers/configuration_spec.rb +41 -5
  39. data/spec/killbill/helpers/payment_plugin_spec.rb +4 -1
  40. data/spec/killbill/helpers/private_payment_plugin_spec.rb +4 -2
  41. data/spec/killbill/helpers/utils_spec.rb +6 -6
  42. data/spec/killbill/invoice_plugin_api_spec.rb +1 -1
  43. data/spec/killbill/notification_plugin_api_spec.rb +1 -1
  44. data/spec/killbill/payment_plugin_api_spec.rb +1 -1
  45. metadata +19 -3
  46. data/lib/killbill/jnotification.rb +0 -31
@@ -28,10 +28,13 @@
28
28
  require 'killbill/gen/plugin-api/payment_method_info_plugin'
29
29
  require 'killbill/gen/plugin-api/payment_plugin_api'
30
30
  require 'killbill/gen/plugin-api/payment_plugin_api_exception'
31
+ require 'killbill/gen/plugin-api/payment_plugin_with_events_api'
31
32
  require 'killbill/gen/plugin-api/ext_bus_event'
32
33
  require 'killbill/gen/plugin-api/notification_plugin_api'
33
34
  require 'killbill/gen/plugin-api/invoice_plugin_api'
35
+ require 'killbill/gen/plugin-api/invoice_plugin_with_events_api'
34
36
  require 'killbill/gen/plugin-api/currency_plugin_api'
37
+ require 'killbill/gen/plugin-api/currency_plugin_with_events_api'
35
38
  require 'killbill/gen/plugin-api/gateway_notification'
36
39
  require 'killbill/gen/plugin-api/hosted_payment_page_form_descriptor'
37
40
  require 'killbill/gen/plugin-api/payment_transaction_info_plugin'
@@ -1,73 +1,143 @@
1
1
  require 'logger'
2
+ require 'thread_safe'
2
3
 
3
4
  module Killbill
4
5
  module Plugin
5
6
  module ActiveMerchant
6
- mattr_reader :config
7
- mattr_reader :currency_conversions
8
- mattr_reader :gateways
7
+
8
+ mattr_reader :glob_config
9
+ mattr_reader :glob_currency_conversions
10
+
9
11
  mattr_reader :initialized
10
12
  mattr_reader :kb_apis
13
+ mattr_reader :gateway_name
14
+ mattr_reader :gateway_builder
11
15
  mattr_reader :logger
16
+ mattr_reader :config_key_name
17
+ mattr_reader :per_tenant_config_cache
12
18
 
13
- def self.initialize!(gateway_builder, gateway_name, logger, config_file, kb_apis)
14
- @@config = Properties.new(config_file)
15
- @@config.parse!
16
19
 
17
- @@logger = logger
18
- @@logger.log_level = Logger::DEBUG if (@@config[:logger] || {})[:debug]
20
+ class << self
19
21
 
20
- @@currency_conversions = @@config[:currency_conversions]
21
- @@kb_apis = kb_apis
22
+ def initialize!(gateway_builder, gateway_name, logger, config_key_name, config_file, kb_apis)
22
23
 
23
- @@gateways = {}
24
- gateway_configs = @@config[gateway_name.to_sym]
25
- if gateway_configs.is_a?(Array)
26
- default_gateway = nil
27
- gateway_configs.each_with_index do |gateway_config, idx|
28
- gateway_account_id = gateway_config[:account_id]
29
- if gateway_account_id.nil?
30
- @@logger.warn "Skipping config #{gateway_config} -- missing :account_id"
31
- else
32
- @@gateways[gateway_account_id.to_sym] = Gateway.wrap(gateway_builder, logger, gateway_config)
33
- default_gateway = @@gateways[gateway_account_id.to_sym] if idx == 0
24
+ @@logger = logger
25
+ @@kb_apis = kb_apis
26
+ @@gateway_name = gateway_name
27
+ @@gateway_builder = gateway_builder
28
+ @@config_key_name = config_key_name
29
+ @@per_tenant_config_cache = ThreadSafe::Cache.new
30
+
31
+ if defined?(JRUBY_VERSION)
32
+ begin
33
+ # See https://github.com/jruby/activerecord-jdbc-adapter/issues/302
34
+ require 'jdbc/mysql'
35
+ ::Jdbc::MySQL.load_driver(:require) if ::Jdbc::MySQL.respond_to?(:load_driver)
36
+ rescue => e
37
+ @@logger.warn "Unable to load the JDBC driver: #{e}"
34
38
  end
35
39
  end
36
- @@gateways[:default] = default_gateway if @@gateways[:default].nil?
37
- else
38
- @@gateways[:default] = Gateway.wrap(gateway_builder, logger, gateway_configs)
40
+
41
+ initialize_from_global_config!(gateway_builder, gateway_name, logger, config_file)
39
42
  end
40
43
 
41
- if defined?(JRUBY_VERSION)
42
- begin
43
- # See https://github.com/jruby/activerecord-jdbc-adapter/issues/302
44
- require 'jdbc/mysql'
45
- ::Jdbc::MySQL.load_driver(:require) if ::Jdbc::MySQL.respond_to?(:load_driver)
46
- rescue => e
47
- @@logger.warn "Unable to load the JDBC driver: #{e}"
44
+
45
+ def gateways(kb_tenant_id=nil)
46
+ tenant_config = get_tenant_config(kb_tenant_id)
47
+ extract_gateway_config(tenant_config)
48
+ end
49
+
50
+ def currency_conversions(kb_tenant_id=nil)
51
+ tenant_config = get_tenant_config(kb_tenant_id)
52
+ if tenant_config
53
+ tenant_config[:currency_conversions]
54
+ else
55
+ @@glob_currency_conversions
48
56
  end
49
57
  end
50
58
 
51
- begin
52
- require 'active_record'
53
- ::ActiveRecord::Base.establish_connection(@@config[:database])
54
- ::ActiveRecord::Base.logger = @@logger
55
- rescue => e
56
- @@logger.warn "Unable to establish a database connection: #{e}"
59
+ def config(kb_tenant_id=nil)
60
+ get_tenant_config(kb_tenant_id)
57
61
  end
58
62
 
59
- # Configure the ActiveMerchant HTTP backend
60
- connection_type = (@@config[:active_merchant] || {})[:connection_type]
61
- if connection_type == :typhoeus
62
- require 'killbill/ext/active_merchant/typhoeus_connection'
63
+ def converted_currency(currency, kb_tenant_id=nil)
64
+ currency_sym = currency.to_s.upcase.to_sym
65
+ tmp = currency_conversions(kb_tenant_id)
66
+ tmp && tmp[currency_sym]
63
67
  end
64
68
 
65
- @@initialized = true
66
- end
69
+ def invalidate_tenant_config!(kb_tenant_id)
70
+ @@logger.info("Invalidate plugin key #{@@config_key_name}, tenant = #{kb_tenant_id}")
71
+ @@per_tenant_config_cache[kb_tenant_id] = nil
72
+ end
73
+
74
+ private
75
+
76
+ def extract_gateway_config(config)
77
+ gateways_config = {}
78
+ gateway_configs = config[@@gateway_name.to_sym]
79
+ if gateway_configs.is_a?(Array)
80
+ default_gateway = nil
81
+ gateway_configs.each_with_index do |gateway_config, idx|
82
+ gateway_account_id = gateway_config[:account_id]
83
+ if gateway_account_id.nil?
84
+ @@logger.warn "Skipping config #{gateway_config} -- missing :account_id"
85
+ else
86
+ gateways_config[gateway_account_id.to_sym] = Gateway.wrap(gateway_builder, logger, gateway_config)
87
+ default_gateway = gateways_config[gateway_account_id.to_sym] if idx == 0
88
+ end
89
+ end
90
+ gateways_config[:default] = default_gateway if gateways_config[:default].nil?
91
+ else
92
+ gateways_config[:default] = Gateway.wrap(@@gateway_builder, logger, gateway_configs)
93
+ end
94
+ gateways_config
95
+ end
67
96
 
68
- def self.converted_currency(currency)
69
- currency_sym = currency.to_s.upcase.to_sym
70
- @@currency_conversions && @@currency_conversions[currency_sym]
97
+ def get_tenant_config(kb_tenant_id)
98
+
99
+ if @@per_tenant_config_cache[kb_tenant_id].nil?
100
+ # Make the api api to verify if there is a per tenant value
101
+ context = @@kb_apis.create_context(kb_tenant_id) if kb_tenant_id
102
+ values = @@kb_apis.tenant_user_api.get_tenant_values_for_key(@@config_key_name, context) if context
103
+ # If we have a per tenant value, insert it into the cache
104
+ if values && values[0]
105
+ parsed_config = YAML.load(values[0])
106
+ @@per_tenant_config_cache[kb_tenant_id] = parsed_config
107
+ # Otherwise, add global config so we don't have to make the tenant call on each operation
108
+ else
109
+ @@per_tenant_config_cache[kb_tenant_id] = @@glob_config
110
+ end
111
+ end
112
+ # Return value from cache in any case
113
+ @@per_tenant_config_cache[kb_tenant_id]
114
+ end
115
+
116
+ def initialize_from_global_config!(gateway_builder, gateway_name, logger, config_file)
117
+ # Look for global config
118
+ @@glob_config = Properties.new(config_file)
119
+ @@glob_config.parse!
120
+
121
+ @@logger.log_level = Logger::DEBUG if (@@glob_config[:logger] || {})[:debug]
122
+
123
+ @@glob_currency_conversions = @@glob_config[:currency_conversions]
124
+
125
+ begin
126
+ require 'active_record'
127
+ ::ActiveRecord::Base.establish_connection(@@glob_config[:database])
128
+ ::ActiveRecord::Base.logger = @@logger
129
+ rescue => e
130
+ @@logger.warn "Unable to establish a database connection: #{e}"
131
+ end
132
+
133
+ # Configure the ActiveMerchant HTTP backend
134
+ connection_type = (@@glob_config[:active_merchant] || {})[:connection_type]
135
+ if connection_type == :typhoeus
136
+ require 'killbill/ext/active_merchant/typhoeus_connection'
137
+ end
138
+
139
+ @@initialized = true
140
+ end
71
141
  end
72
142
  end
73
143
  end
@@ -13,18 +13,18 @@ module Killbill
13
13
  create_kb_account kb_account_id
14
14
  end
15
15
 
16
- context = @plugin.kb_apis.create_context(kb_tenant_id)
17
- account = @plugin.kb_apis.account_user_api.get_account_by_id(kb_account_id, context)
16
+ context = @plugin.kb_apis.create_context(kb_tenant_id)
17
+ account = @plugin.kb_apis.account_user_api.get_account_by_id(kb_account_id, context)
18
18
 
19
19
  # The rest is pure Ruby
20
- context = context.to_ruby(context)
20
+ context = context.to_ruby(context)
21
21
 
22
22
  # Generate a token
23
23
  pm_properties = build_pm_properties(account, options)
24
24
 
25
- info = Killbill::Plugin::Model::PaymentMethodPlugin.new
25
+ info = Killbill::Plugin::Model::PaymentMethodPlugin.new
26
26
  info.properties = pm_properties
27
- payment_method = @plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, properties, context)
27
+ payment_method = @plugin.add_payment_method(kb_account_id, kb_payment_method_id, info, true, properties, context)
28
28
 
29
29
  pm = payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)
30
30
  pm.should == payment_method
@@ -35,19 +35,19 @@ module Killbill
35
35
  end
36
36
 
37
37
  def build_pm_properties(account = nil, overrides = {})
38
- cc_number = (overrides.delete(:cc_number) || '4242424242424242')
39
- cc_first_name = (overrides.delete(:cc_first_name) || 'John')
40
- cc_last_name = (overrides.delete(:cc_last_name) || 'Doe')
41
- cc_type = (overrides.delete(:cc_type) || 'Visa')
42
- cc_exp_month = (overrides.delete(:cc_exp_month) || 12)
43
- cc_exp_year = (overrides.delete(:cc_exp_year) || 2017)
44
- cc_last_4 = (overrides.delete(:cc_last_4) || 4242)
45
- address1 = (overrides.delete(:address1) || '5, oakriu road')
46
- address2 = (overrides.delete(:address2) || 'apt. 298')
47
- city = (overrides.delete(:city) || 'Gdio Foia')
48
- state = (overrides.delete(:state) || 'FL')
49
- zip = (overrides.delete(:zip) || 49302)
50
- country = (overrides.delete(:country) || 'US')
38
+ cc_number = (overrides.delete(:cc_number) || '4242424242424242')
39
+ cc_first_name = (overrides.delete(:cc_first_name) || 'John')
40
+ cc_last_name = (overrides.delete(:cc_last_name) || 'Doe')
41
+ cc_type = (overrides.delete(:cc_type) || 'Visa')
42
+ cc_exp_month = (overrides.delete(:cc_exp_month) || 12)
43
+ cc_exp_year = (overrides.delete(:cc_exp_year) || 2017)
44
+ cc_last_4 = (overrides.delete(:cc_last_4) || 4242)
45
+ address1 = (overrides.delete(:address1) || '5, oakriu road')
46
+ address2 = (overrides.delete(:address2) || 'apt. 298')
47
+ city = (overrides.delete(:city) || 'Gdio Foia')
48
+ state = (overrides.delete(:state) || 'FL')
49
+ zip = (overrides.delete(:zip) || 49302)
50
+ country = (overrides.delete(:country) || 'US')
51
51
  cc_verification_value = (overrides.delete(:cc_verification_value) || 1234)
52
52
 
53
53
  properties = []
@@ -76,14 +76,14 @@ module Killbill
76
76
 
77
77
  def create_kb_account(kb_account_id)
78
78
  external_key = Time.now.to_i.to_s + '-test'
79
- email = external_key + '@tester.com'
79
+ email = external_key + '@tester.com'
80
80
 
81
- account = ::Killbill::Plugin::Model::Account.new
82
- account.id = kb_account_id
81
+ account = ::Killbill::Plugin::Model::Account.new
82
+ account.id = kb_account_id
83
83
  account.external_key = external_key
84
- account.email = email
85
- account.name = 'Integration spec'
86
- account.currency = :USD
84
+ account.email = email
85
+ account.name = 'Integration spec'
86
+ account.currency = :USD
87
87
 
88
88
  @account_api.accounts << account
89
89
 
@@ -91,8 +91,8 @@ module Killbill
91
91
  end
92
92
 
93
93
  def create_pm_kv_info(key, value)
94
- prop = ::Killbill::Plugin::Model::PluginProperty.new
95
- prop.key = key
94
+ prop = ::Killbill::Plugin::Model::PluginProperty.new
95
+ prop.key = key
96
96
  prop.value = value
97
97
  prop
98
98
  end
@@ -124,17 +124,17 @@ module Killbill
124
124
  def add_payment(kb_payment_id=SecureRandom.uuid, kb_payment_transaction_id=SecureRandom.uuid, kb_payment_transaction_external_key=SecureRandom.uuid, transaction_type=:PURCHASE)
125
125
  kb_payment = get_payment kb_payment_id
126
126
  if kb_payment.nil?
127
- kb_payment = ::Killbill::Plugin::Model::Payment.new
128
- kb_payment.id = kb_payment_id
127
+ kb_payment = ::Killbill::Plugin::Model::Payment.new
128
+ kb_payment.id = kb_payment_id
129
129
  kb_payment.transactions = []
130
130
  @payments << kb_payment
131
131
  end
132
132
 
133
- kb_payment_transaction = ::Killbill::Plugin::Model::PaymentTransaction.new
134
- kb_payment_transaction.id = kb_payment_transaction_id
133
+ kb_payment_transaction = ::Killbill::Plugin::Model::PaymentTransaction.new
134
+ kb_payment_transaction.id = kb_payment_transaction_id
135
135
  kb_payment_transaction.transaction_type = transaction_type
136
- kb_payment_transaction.external_key = kb_payment_transaction_external_key
137
- kb_payment_transaction.created_date = Java::org.joda.time.DateTime.new(Java::org.joda.time.DateTimeZone::UTC)
136
+ kb_payment_transaction.external_key = kb_payment_transaction_external_key
137
+ kb_payment_transaction.created_date = Java::org.joda.time.DateTime.new(Java::org.joda.time.DateTimeZone::UTC)
138
138
  kb_payment.transactions << kb_payment_transaction
139
139
 
140
140
  kb_payment
@@ -144,6 +144,23 @@ module Killbill
144
144
  @payments.find { |payment| payment.id == id.to_s }
145
145
  end
146
146
  end
147
+
148
+ class FakeJavaTenantUserApi
149
+
150
+ attr_accessor :per_tenant_config
151
+
152
+ def initialize(per_tenant_config)
153
+ @per_tenant_config = per_tenant_config
154
+ end
155
+
156
+ def get_tenant_values_for_key(key, context)
157
+ result = @per_tenant_config[context.tenant_id.to_s]
158
+ if result
159
+ return [result]
160
+ end
161
+ nil
162
+ end
163
+ end
147
164
  end
148
165
  end
149
166
  end
@@ -17,14 +17,18 @@ module Killbill
17
17
  @payment_method_model = payment_method_model
18
18
  @transaction_model = transaction_model
19
19
  @response_model = response_model
20
+
20
21
  end
21
22
 
22
23
  def start_plugin
24
+
23
25
  @logger.progname = "#{@identifier.to_s}-plugin"
24
26
 
27
+ @config_key_name = "PLUGIN_CONFIG_#{@plugin_name}".to_sym
25
28
  ::Killbill::Plugin::ActiveMerchant.initialize! @gateway_builder,
26
29
  @identifier.to_sym,
27
30
  @logger,
31
+ @config_key_name,
28
32
  "#{@conf_dir}/#{@identifier.to_s}.yml",
29
33
  @kb_apis
30
34
 
@@ -45,6 +49,14 @@ module Killbill
45
49
  @logger.debug { "after_request: pool.active_connection? = #{pool.active_connection?}, connection.active? = #{connection.active?}, pool.connections.size = #{pool.connections.size}, connections = #{pool.connections.inspect}" }
46
50
  end
47
51
 
52
+
53
+ def on_event(event)
54
+ if (event.event_type == :TENANT_CONFIG_CHANGE || event.event_type == :TENANT_CONFIG_DELETION) &&
55
+ event.meta_data.to_sym == @config_key_name
56
+ ::Killbill::Plugin::ActiveMerchant.invalidate_tenant_config!(event.tenant_id)
57
+ end
58
+ end
59
+
48
60
  def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
49
61
  gateway_call_proc = Proc.new do |gateway, linked_transaction, payment_source, amount_in_cents, options|
50
62
  gateway.authorize(amount_in_cents, payment_source, options)
@@ -161,7 +173,7 @@ module Killbill
161
173
 
162
174
  # Go to the gateway
163
175
  payment_processor_account_id = options[:payment_processor_account_id] || :default
164
- gateway = lookup_gateway(payment_processor_account_id)
176
+ gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
165
177
  gw_response = gateway.store(payment_source, options)
166
178
  response, transaction = save_response_and_transaction(gw_response, :add_payment_method, kb_account_id, context.tenant_id, payment_processor_account_id)
167
179
 
@@ -189,7 +201,7 @@ module Killbill
189
201
 
190
202
  # Delete the card
191
203
  payment_processor_account_id = options[:payment_processor_account_id] || :default
192
- gateway = lookup_gateway(payment_processor_account_id)
204
+ gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
193
205
  if options[:customer_id]
194
206
  gw_response = gateway.unstore(options[:customer_id], pm.token, options)
195
207
  else
@@ -395,7 +407,7 @@ module Killbill
395
407
  payment_processor_account_ids = options[:payment_processor_account_ids].nil? ? [options[:payment_processor_account_id] || :default] : options[:payment_processor_account_ids].split(',')
396
408
  payment_processor_account_ids.each do |payment_processor_account_id|
397
409
  # Find the gateway
398
- gateway = lookup_gateway(payment_processor_account_id)
410
+ gateway = lookup_gateway(payment_processor_account_id, context.tenant_id)
399
411
 
400
412
  # Filter before each gateway call
401
413
  before_gateway(gateway, kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options)
@@ -523,9 +535,9 @@ module Killbill
523
535
  return response, transaction
524
536
  end
525
537
 
526
- def lookup_gateway(payment_processor_account_id=:default)
527
- gateway = ::Killbill::Plugin::ActiveMerchant.gateways[payment_processor_account_id.to_sym]
528
- raise "Unable to lookup gateway for payment_processor_account_id #{payment_processor_account_id}, gateways: #{::Killbill::Plugin::ActiveMerchant.gateways}" if gateway.nil?
538
+ def lookup_gateway(payment_processor_account_id=:default, kb_tenant_id=nil)
539
+ gateway = ::Killbill::Plugin::ActiveMerchant.gateways(kb_tenant_id)[payment_processor_account_id.to_sym]
540
+ raise "Unable to lookup gateway for payment_processor_account_id #{payment_processor_account_id}, kb_tenant_id = #{kb_tenant_id}, gateways: #{::Killbill::Plugin::ActiveMerchant.gateways(kb_tenant_id)}" if gateway.nil?
529
541
  gateway
530
542
  end
531
543
 
@@ -85,9 +85,9 @@ module Killbill
85
85
  ::Killbill::Plugin::ActiveMerchant.kb_apis
86
86
  end
87
87
 
88
- def gateway(payment_processor_account_id=:default)
89
- gateway = ::Killbill::Plugin::ActiveMerchant.gateways[payment_processor_account_id.to_sym]
90
- raise "Unable to lookup gateway for payment_processor_account_id #{payment_processor_account_id}, gateways: #{::Killbill::Plugin::ActiveMerchant.gateways}" if gateway.nil?
88
+ def gateway(payment_processor_account_id=:default, kb_tenant_id=nil)
89
+ gateway = ::Killbill::Plugin::ActiveMerchant.gateways(kb_tenant_id)[payment_processor_account_id.to_sym]
90
+ raise "Unable to lookup gateway for payment_processor_account_id #{payment_processor_account_id}, , kb_tenant_id = #{kb_tenant_id}, gateways: #{::Killbill::Plugin::ActiveMerchant.gateways(kb_tenant_id)}" if gateway.nil?
91
91
  gateway
92
92
  end
93
93
 
@@ -1,3 +1,5 @@
1
+ require 'thread_safe'
2
+
1
3
  module Killbill
2
4
  module Plugin
3
5
  module ActiveMerchant
@@ -46,100 +48,65 @@ module Killbill
46
48
  end
47
49
  end
48
50
 
49
- # Relies on the fact that hashes enumerate their values in the order that the corresponding keys were inserted (Ruby 1.9+)
50
51
  class BoundedLRUCache
52
+ include ThreadSafe::Util::CheapLockable
51
53
 
52
- def initialize(proc, max_size=10000)
53
- @proc = proc
54
+ def initialize(proc, max_size = 10000)
54
55
  @max_size = max_size
55
-
56
- if defined?(JRUBY_VERSION)
57
- @is_jruby = true
58
- @semaphore = nil
59
-
60
- lru_cache = Class.new(java.util.LinkedHashMap) do
61
- def initialize(max_size)
62
- super(max_size, 1.0, true)
63
- @max_size = max_size
64
- end
65
-
66
- # Note: renaming it to remove_eldest_entry won't work
67
- def removeEldestEntry(eldest)
68
- size > @max_size
69
- end
70
- end.new(@max_size)
71
- @data = java.util.Collections.synchronizedMap(lru_cache)
72
- else
73
- @is_jruby = false
74
- @semaphore = Mutex.new
75
- # TODO Pre-allocate?
76
- @data = {}
56
+ @data = ThreadSafe::Cache.new do |hash, key|
57
+ # mapping key -> value is constant for our purposes
58
+ set_value = hash.fetch_or_store key, value = proc.call(key)
59
+ store_key(key) if value.equal?(set_value) # very same object
60
+ set_value
77
61
  end
62
+ @keys = []
78
63
  end
79
64
 
80
- def [](key)
81
- @is_jruby ? jruby_get(key) : ruby_get(key)
82
- end
65
+ def [](key); @data[key] end
83
66
 
84
67
  def []=(key, val)
85
- @is_jruby ? jruby_set(key, val) : ruby_set(key, val)
68
+ prev_val = @data.get_and_set(key, val)
69
+ prev_val.nil? ? store_key(key) : update_key(key)
70
+ val
86
71
  end
87
72
 
88
- # For testing
73
+ # @private for testing
74
+ def size; @data.size end
89
75
 
90
- def size
91
- @data.size
76
+ # @private for testing
77
+ def keys
78
+ cheap_synchronize { @keys.dup }
92
79
  end
93
80
 
94
- def keys_to_a
95
- @is_jruby ? @data.key_set.to_a : @data.keys
81
+ # @private for testing
82
+ def values
83
+ cheap_synchronize { @keys.map { |key| self[key] } }
96
84
  end
97
85
 
98
- def values_to_a
99
- @is_jruby ? @data.values.to_a : @data.values
100
- end
86
+ protected
101
87
 
102
- private
103
-
104
- def jruby_get(key)
105
- value = @data.get(key)
106
- if value.nil?
107
- value = @proc.call(key)
108
- # Somebody may have beaten us to it but the mapping key -> value is constant for our purposes
109
- jruby_set(key, value)
88
+ def store_key(key)
89
+ cheap_synchronize do
90
+ @keys << key
91
+ remove_eldest_key_if_full
110
92
  end
111
- value
112
93
  end
113
94
 
114
- def jruby_set(key, val)
115
- @data.put(key, val)
116
- end
117
-
118
- def ruby_get(key)
119
- @semaphore.synchronize do
120
- found = true
121
- value = @data.delete(key) { found = false }
122
- if found
123
- @data[key] = value
124
- else
125
- value = @proc.call(key)
126
- @data[key] = value
127
- value
128
- end
95
+ def update_key(key)
96
+ cheap_synchronize do
97
+ @keys.delete(key); @keys << key
98
+ remove_eldest_key_if_full
129
99
  end
130
100
  end
131
101
 
132
- def ruby_set(key, val)
133
- @semaphore.synchronize do
134
- @data.delete(key)
135
- @data[key] = val
136
- if @data.length > @max_size
137
- @data.delete(@data.first[0])
138
- end
139
- val
140
- end
102
+ private
103
+
104
+ def remove_eldest_key_if_full
105
+ @data.delete @keys.shift if @data.size > @max_size
141
106
  end
107
+
142
108
  end
109
+
143
110
  end
144
111
  end
145
112
  end