Skip to content

Commit 05ebb60

Browse files
feat: add notification center registry (#323)
* add notification center registry
1 parent 92bf3be commit 05ebb60

13 files changed

+304
-79
lines changed

lib/optimizely.rb

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2016-2022, Optimizely and contributors
4+
# Copyright 2016-2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
require_relative 'optimizely/helpers/variable_type'
3737
require_relative 'optimizely/logger'
3838
require_relative 'optimizely/notification_center'
39+
require_relative 'optimizely/notification_center_registry'
3940
require_relative 'optimizely/optimizely_config'
4041
require_relative 'optimizely/optimizely_user_context'
4142
require_relative 'optimizely/odp/lru_cache'
@@ -105,19 +106,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
105106

106107
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
107108

108-
setup_odp!
109-
110-
@odp_manager = OdpManager.new(
111-
disable: @sdk_settings.odp_disabled,
112-
segment_manager: @sdk_settings.odp_segment_manager,
113-
event_manager: @sdk_settings.odp_event_manager,
114-
segments_cache: @sdk_settings.odp_segments_cache,
115-
fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
116-
odp_event_timeout: @sdk_settings.odp_event_timeout,
117-
logger: @logger
118-
)
119-
120-
@config_manager = if config_manager.respond_to?(:config)
109+
@config_manager = if config_manager.respond_to?(:config) && config_manager.respond_to?(:sdk_key)
121110
config_manager
122111
elsif sdk_key
123112
HTTPProjectConfigManager.new(
@@ -132,9 +121,7 @@ def initialize( # rubocop:disable Metrics/ParameterLists
132121
StaticProjectConfigManager.new(datafile, @logger, @error_handler, skip_json_validation)
133122
end
134123

135-
# must call this even if it's scheduled as a listener
136-
# in case the config manager was initialized before the listener was added
137-
update_odp_config_on_datafile_update unless @sdk_settings.odp_disabled
124+
setup_odp!(@config_manager.sdk_key)
138125

139126
@decision_service = DecisionService.new(@logger, @user_profile_service)
140127

@@ -1171,7 +1158,7 @@ def project_config
11711158
end
11721159

11731160
def update_odp_config_on_datafile_update
1174-
# if datafile isn't ready, expects to be called again by the notification_center
1161+
# if datafile isn't ready, expects to be called again by the internal notification_center
11751162
return if @config_manager.respond_to?(:ready?) && !@config_manager.ready?
11761163

11771164
config = @config_manager&.config
@@ -1180,19 +1167,12 @@ def update_odp_config_on_datafile_update
11801167
@odp_manager.update_odp_config(config.public_key_for_odp, config.host_for_odp, config.all_segments)
11811168
end
11821169

1183-
def setup_odp!
1170+
def setup_odp!(sdk_key)
11841171
unless @sdk_settings.is_a? Optimizely::Helpers::OptimizelySdkSettings
11851172
@logger.log(Logger::DEBUG, 'Provided sdk_settings is not an OptimizelySdkSettings instance.') unless @sdk_settings.nil?
11861173
@sdk_settings = Optimizely::Helpers::OptimizelySdkSettings.new
11871174
end
11881175

1189-
return if @sdk_settings.odp_disabled
1190-
1191-
@notification_center.add_notification_listener(
1192-
NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE],
1193-
-> { update_odp_config_on_datafile_update }
1194-
)
1195-
11961176
if !@sdk_settings.odp_segment_manager.nil? && !Helpers::Validator.segment_manager_valid?(@sdk_settings.odp_segment_manager)
11971177
@logger.log(Logger::ERROR, 'Invalid ODP segment manager, reverting to default.')
11981178
@sdk_settings.odp_segment_manager = nil
@@ -1203,17 +1183,39 @@ def setup_odp!
12031183
@sdk_settings.odp_event_manager = nil
12041184
end
12051185

1206-
return if @sdk_settings.odp_segment_manager
1207-
12081186
if !@sdk_settings.odp_segments_cache.nil? && !Helpers::Validator.segments_cache_valid?(@sdk_settings.odp_segments_cache)
12091187
@logger.log(Logger::ERROR, 'Invalid ODP segments cache, reverting to default.')
12101188
@sdk_settings.odp_segments_cache = nil
12111189
end
12121190

1213-
@sdk_settings.odp_segments_cache ||= LRUCache.new(
1214-
@sdk_settings.segments_cache_size,
1215-
@sdk_settings.segments_cache_timeout_in_secs
1191+
# no need to instantiate a cache if a custom cache or segment manager is provided.
1192+
if !@sdk_settings.odp_disabled && @sdk_settings.odp_segment_manager.nil?
1193+
@sdk_settings.odp_segments_cache ||= LRUCache.new(
1194+
@sdk_settings.segments_cache_size,
1195+
@sdk_settings.segments_cache_timeout_in_secs
1196+
)
1197+
end
1198+
1199+
@odp_manager = OdpManager.new(
1200+
disable: @sdk_settings.odp_disabled,
1201+
segment_manager: @sdk_settings.odp_segment_manager,
1202+
event_manager: @sdk_settings.odp_event_manager,
1203+
segments_cache: @sdk_settings.odp_segments_cache,
1204+
fetch_segments_timeout: @sdk_settings.fetch_segments_timeout,
1205+
odp_event_timeout: @sdk_settings.odp_event_timeout,
1206+
logger: @logger
12161207
)
1208+
1209+
return if @sdk_settings.odp_disabled
1210+
1211+
Optimizely::NotificationCenterRegistry
1212+
.get_notification_center(sdk_key, @logger)
1213+
&.add_notification_listener(
1214+
NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE],
1215+
method(:update_odp_config_on_datafile_update)
1216+
)
1217+
1218+
update_odp_config_on_datafile_update
12171219
end
12181220
end
12191221
end

lib/optimizely/config_manager/http_project_config_manager.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2019-2020, 2022, Optimizely and contributors
4+
# Copyright 2019-2020, 2022-2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -33,12 +33,12 @@ module Optimizely
3333
class HTTPProjectConfigManager < ProjectConfigManager
3434
# Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
3535

36-
attr_reader :stopped
36+
attr_reader :stopped, :sdk_key
3737

3838
# Initialize config manager. One of sdk_key or url has to be set to be able to use.
3939
#
40-
# sdk_key - Optional string uniquely identifying the datafile. It's required unless a URL is passed in.
41-
# datafile: Optional JSON string representing the project.
40+
# sdk_key - Optional string uniquely identifying the datafile. It's required unless a datafile with sdk_key is passed in.
41+
# datafile - Optional JSON string representing the project. If nil, sdk_key is required.
4242
# polling_interval - Optional floating point number representing time interval in seconds
4343
# at which to request datafile and set ProjectConfig.
4444
# blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
@@ -83,6 +83,10 @@ def initialize(
8383
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
8484
@optimizely_config = nil
8585
@config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
86+
@sdk_key = sdk_key || @config&.sdk_key
87+
88+
raise MissingSdkKeyError if @sdk_key.nil?
89+
8690
@mutex = Mutex.new
8791
@resource = ConditionVariable.new
8892
@async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
@@ -222,6 +226,10 @@ def set_config(config)
222226

223227
@notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
224228

229+
NotificationCenterRegistry
230+
.get_notification_center(@sdk_key, @logger)
231+
&.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
232+
225233
@logger.log(Logger::DEBUG, 'Received new datafile and updated config. ' \
226234
"Old revision number: #{previous_revision}. New revision number: #{@config.revision}.")
227235
end

lib/optimizely/config_manager/project_config_manager.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2019, Optimizely and contributors
4+
# Copyright 2019, 2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -20,5 +20,6 @@ class ProjectConfigManager
2020
# Interface for fetching ProjectConfig instance.
2121

2222
def config; end
23+
def sdk_key; end
2324
end
2425
end

lib/optimizely/config_manager/static_project_config_manager.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2019-2020, 2022, Optimizely and contributors
4+
# Copyright 2019-2020, 2022-2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
2323
module Optimizely
2424
class StaticProjectConfigManager < ProjectConfigManager
2525
# Implementation of ProjectConfigManager interface.
26-
attr_reader :config
26+
attr_reader :config, :sdk_key
2727

2828
def initialize(datafile, logger, error_handler, skip_json_validation)
2929
# Looks up and sets datafile and config based on response body.
@@ -41,6 +41,7 @@ def initialize(datafile, logger, error_handler, skip_json_validation)
4141
error_handler,
4242
skip_json_validation
4343
)
44+
@sdk_key = @config&.sdk_key
4445
@optimizely_config = nil
4546
end
4647

lib/optimizely/exceptions.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2016-2020, 2022, Optimizely and contributors
4+
# Copyright 2016-2020, 2022-2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -32,6 +32,13 @@ def initialize(msg = 'Provided URI was invalid.')
3232
end
3333
end
3434

35+
class MissingSdkKeyError < Error
36+
# Raised when a provided URI is invalid.
37+
def initialize(msg = 'SDK key not provided/cannot be found in the datafile.')
38+
super
39+
end
40+
end
41+
3542
class InvalidAudienceError < Error
3643
# Raised when an invalid audience is provided
3744

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2023, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
require_relative 'notification_center'
19+
require_relative 'exceptions'
20+
21+
module Optimizely
22+
class NotificationCenterRegistry
23+
private_class_method :new
24+
# Class managing internal notification centers.
25+
# @api no-doc
26+
@notification_centers = {}
27+
@mutex = Mutex.new
28+
29+
# Returns an internal notification center for the given sdk_key, creating one
30+
# if none exists yet.
31+
#
32+
# Args:
33+
# sdk_key: A string sdk key to uniquely identify the notification center.
34+
# logger: Optional logger.
35+
36+
# Returns:
37+
# nil or NotificationCenter
38+
def self.get_notification_center(sdk_key, logger)
39+
unless sdk_key
40+
logger&.log(Logger::ERROR, "#{MissingSdkKeyError.new.message} ODP may not work properly without it.")
41+
return nil
42+
end
43+
44+
notification_center = nil
45+
46+
@mutex.synchronize do
47+
if @notification_centers.key?(sdk_key)
48+
notification_center = @notification_centers[sdk_key]
49+
else
50+
notification_center = NotificationCenter.new(logger, nil)
51+
@notification_centers[sdk_key] = notification_center
52+
end
53+
end
54+
55+
notification_center
56+
end
57+
58+
# Remove a previously added notification center and clear all its listeners.
59+
60+
# Args:
61+
# sdk_key: The sdk_key of the notification center to remove.
62+
def self.remove_notification_center(sdk_key)
63+
@mutex.synchronize do
64+
@notification_centers
65+
.delete(sdk_key)
66+
&.clear_all_notification_listeners
67+
end
68+
nil
69+
end
70+
end
71+
end

spec/config_manager/http_project_config_manager_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2019-2020, 2022, Optimizely and contributors
4+
# Copyright 2019-2020, 2022-2023, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -57,6 +57,7 @@
5757
describe '.project_config_manager' do
5858
it 'should get project config when valid url is given' do
5959
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
60+
sdk_key: 'valid_sdk_key',
6061
url: 'https://cdn.optimizely.com/datafiles/valid_sdk_key.json'
6162
)
6263

@@ -75,6 +76,7 @@
7576
.to_return(status: 200, body: VALID_SDK_KEY_CONFIG_JSON, headers: {})
7677

7778
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
79+
sdk_key: 'valid_sdk_key',
7880
url: 'http://cdn.optimizely.com/datafiles/valid_sdk_key.json'
7981
)
8082

0 commit comments

Comments
 (0)