optimizely-sdk 3.10.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -202
  3. data/lib/optimizely/audience.rb +127 -97
  4. data/lib/optimizely/bucketer.rb +156 -156
  5. data/lib/optimizely/condition_tree_evaluator.rb +123 -123
  6. data/lib/optimizely/config/datafile_project_config.rb +539 -552
  7. data/lib/optimizely/config/proxy_config.rb +34 -34
  8. data/lib/optimizely/config_manager/async_scheduler.rb +95 -95
  9. data/lib/optimizely/config_manager/http_project_config_manager.rb +330 -329
  10. data/lib/optimizely/config_manager/project_config_manager.rb +24 -24
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +53 -52
  12. data/lib/optimizely/decide/optimizely_decide_option.rb +28 -28
  13. data/lib/optimizely/decide/optimizely_decision.rb +60 -60
  14. data/lib/optimizely/decide/optimizely_decision_message.rb +26 -26
  15. data/lib/optimizely/decision_service.rb +563 -563
  16. data/lib/optimizely/error_handler.rb +39 -39
  17. data/lib/optimizely/event/batch_event_processor.rb +235 -234
  18. data/lib/optimizely/event/entity/conversion_event.rb +44 -43
  19. data/lib/optimizely/event/entity/decision.rb +38 -38
  20. data/lib/optimizely/event/entity/event_batch.rb +86 -86
  21. data/lib/optimizely/event/entity/event_context.rb +50 -50
  22. data/lib/optimizely/event/entity/impression_event.rb +48 -47
  23. data/lib/optimizely/event/entity/snapshot.rb +33 -33
  24. data/lib/optimizely/event/entity/snapshot_event.rb +48 -48
  25. data/lib/optimizely/event/entity/user_event.rb +22 -22
  26. data/lib/optimizely/event/entity/visitor.rb +36 -35
  27. data/lib/optimizely/event/entity/visitor_attribute.rb +38 -37
  28. data/lib/optimizely/event/event_factory.rb +156 -155
  29. data/lib/optimizely/event/event_processor.rb +25 -25
  30. data/lib/optimizely/event/forwarding_event_processor.rb +44 -43
  31. data/lib/optimizely/event/user_event_factory.rb +88 -88
  32. data/lib/optimizely/event_builder.rb +221 -228
  33. data/lib/optimizely/event_dispatcher.rb +71 -71
  34. data/lib/optimizely/exceptions.rb +135 -139
  35. data/lib/optimizely/helpers/constants.rb +415 -397
  36. data/lib/optimizely/helpers/date_time_utils.rb +30 -30
  37. data/lib/optimizely/helpers/event_tag_utils.rb +132 -132
  38. data/lib/optimizely/helpers/group.rb +31 -31
  39. data/lib/optimizely/helpers/http_utils.rb +65 -64
  40. data/lib/optimizely/helpers/validator.rb +183 -183
  41. data/lib/optimizely/helpers/variable_type.rb +67 -67
  42. data/lib/optimizely/logger.rb +46 -45
  43. data/lib/optimizely/notification_center.rb +174 -176
  44. data/lib/optimizely/optimizely_config.rb +271 -272
  45. data/lib/optimizely/optimizely_factory.rb +181 -181
  46. data/lib/optimizely/optimizely_user_context.rb +204 -179
  47. data/lib/optimizely/params.rb +31 -31
  48. data/lib/optimizely/project_config.rb +99 -91
  49. data/lib/optimizely/semantic_version.rb +166 -166
  50. data/lib/optimizely/{custom_attribute_condition_evaluator.rb → user_condition_evaluator.rb} +391 -369
  51. data/lib/optimizely/user_profile_service.rb +35 -35
  52. data/lib/optimizely/version.rb +21 -21
  53. data/lib/optimizely.rb +1130 -1145
  54. metadata +13 -13
@@ -1,329 +1,330 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright 2019-2020, 2022, 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 '../config/datafile_project_config'
19
- require_relative '../error_handler'
20
- require_relative '../exceptions'
21
- require_relative '../helpers/constants'
22
- require_relative '../helpers/http_utils'
23
- require_relative '../logger'
24
- require_relative '../notification_center'
25
- require_relative '../project_config'
26
- require_relative '../optimizely_config'
27
- require_relative 'project_config_manager'
28
- require_relative 'async_scheduler'
29
-
30
- require 'json'
31
-
32
- module Optimizely
33
- class HTTPProjectConfigManager < ProjectConfigManager
34
- # Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
35
-
36
- attr_reader :stopped
37
-
38
- # Initialize config manager. One of sdk_key or url has to be set to be able to use.
39
- #
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.
42
- # polling_interval - Optional floating point number representing time interval in seconds
43
- # at which to request datafile and set ProjectConfig.
44
- # blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
45
- # auto_update - Boolean indicates to run infinitely or only once.
46
- # start_by_default - Boolean indicates to start by default AsyncScheduler.
47
- # url - Optional string representing URL from where to fetch the datafile. If set it supersedes the sdk_key.
48
- # url_template - Optional string template which in conjunction with sdk_key
49
- # determines URL from where to fetch the datafile.
50
- # logger - Provides a logger instance.
51
- # error_handler - Provides a handle_error method to handle exceptions.
52
- # skip_json_validation - Optional boolean param which allows skipping JSON schema
53
- # validation upon object invocation. By default JSON schema validation will be performed.
54
- # datafile_access_token - access token used to fetch private datafiles
55
- # proxy_config - Optional proxy config instancea to configure making web requests through a proxy server.
56
- def initialize(
57
- sdk_key: nil,
58
- url: nil,
59
- url_template: nil,
60
- polling_interval: nil,
61
- blocking_timeout: nil,
62
- auto_update: true,
63
- start_by_default: true,
64
- datafile: nil,
65
- logger: nil,
66
- error_handler: nil,
67
- skip_json_validation: false,
68
- notification_center: nil,
69
- datafile_access_token: nil,
70
- proxy_config: nil
71
- )
72
- @logger = logger || NoOpLogger.new
73
- @error_handler = error_handler || NoOpErrorHandler.new
74
- @access_token = datafile_access_token
75
- @datafile_url = get_datafile_url(sdk_key, url, url_template)
76
- @polling_interval = nil
77
- polling_interval(polling_interval)
78
- @blocking_timeout = nil
79
- blocking_timeout(blocking_timeout)
80
- @last_modified = nil
81
- @skip_json_validation = skip_json_validation
82
- @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
83
- @optimizely_config = nil
84
- @config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
85
- @mutex = Mutex.new
86
- @resource = ConditionVariable.new
87
- @async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
88
- # Start async scheduler in the end to avoid race condition where scheduler executes
89
- # callback which makes use of variables not yet initialized by the main thread.
90
- @async_scheduler.start! if start_by_default == true
91
- @proxy_config = proxy_config
92
- @stopped = false
93
- end
94
-
95
- def ready?
96
- !@config.nil?
97
- end
98
-
99
- def start!
100
- if @stopped
101
- @logger.log(Logger::WARN, 'Not starting. Already stopped.')
102
- return
103
- end
104
-
105
- @async_scheduler.start!
106
- @stopped = false
107
- end
108
-
109
- def stop!
110
- if @stopped
111
- @logger.log(Logger::WARN, 'Not pausing. Manager has not been started.')
112
- return
113
- end
114
-
115
- @async_scheduler.stop!
116
- @config = nil
117
- @stopped = true
118
- end
119
-
120
- def config
121
- # Get Project Config.
122
-
123
- # if stopped is true, then simply return @config.
124
- # If the background datafile polling thread is running. and config has been initalized,
125
- # we simply return @config.
126
- # If it is not, we wait and block maximum for @blocking_timeout.
127
- # If thread is not running, we fetch the datafile and update config.
128
- return @config if @stopped
129
-
130
- if @async_scheduler.running
131
- return @config if ready?
132
-
133
- @mutex.synchronize do
134
- @resource.wait(@mutex, @blocking_timeout)
135
- return @config
136
- end
137
- end
138
-
139
- fetch_datafile_config
140
- @config
141
- end
142
-
143
- def optimizely_config
144
- @optimizely_config = OptimizelyConfig.new(@config).config if @optimizely_config.nil?
145
-
146
- @optimizely_config
147
- end
148
-
149
- private
150
-
151
- def fetch_datafile_config
152
- # Fetch datafile, handle response and send notification on config update.
153
- config = request_config
154
- return unless config
155
-
156
- set_config config
157
- end
158
-
159
- def request_config
160
- @logger.log(Logger::DEBUG, "Fetching datafile from #{@datafile_url}")
161
- headers = {}
162
- headers['Content-Type'] = 'application/json'
163
- headers['If-Modified-Since'] = @last_modified if @last_modified
164
- headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?
165
-
166
- # Cleaning headers before logging to avoid exposing authorization token
167
- cleansed_headers = {}
168
- headers.each { |key, value| cleansed_headers[key] = key == 'Authorization' ? '********' : value }
169
- @logger.log(Logger::DEBUG, "Datafile request headers: #{cleansed_headers}")
170
-
171
- begin
172
- response = Helpers::HttpUtils.make_request(
173
- @datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT'], @proxy_config
174
- )
175
- rescue StandardError => e
176
- @logger.log(
177
- Logger::ERROR,
178
- "Fetching datafile from #{@datafile_url} failed. Error: #{e}"
179
- )
180
- return nil
181
- end
182
-
183
- response_code = response.code.to_i
184
- @logger.log(Logger::DEBUG, "Datafile response status code #{response_code}")
185
-
186
- # Leave datafile and config unchanged if it has not been modified.
187
- if response.code == '304'
188
- @logger.log(
189
- Logger::DEBUG,
190
- "Not updating config as datafile has not updated since #{@last_modified}."
191
- )
192
- return
193
- end
194
-
195
- if response_code >= 200 && response_code < 400
196
- @logger.log(Logger::DEBUG, 'Successfully fetched datafile, generating Project config')
197
- config = DatafileProjectConfig.create(response.body, @logger, @error_handler, @skip_json_validation)
198
- @last_modified = response[Helpers::Constants::HTTP_HEADERS['LAST_MODIFIED']]
199
- @logger.log(Logger::DEBUG, "Saved last modified header value from response: #{@last_modified}.")
200
- else
201
- @logger.log(Logger::DEBUG, "Datafile fetch failed, status: #{response.code}, message: #{response.message}")
202
- end
203
-
204
- config
205
- end
206
-
207
- def set_config(config)
208
- # Send notification if project config is updated.
209
- previous_revision = @config.revision if @config
210
- return if previous_revision == config.revision
211
-
212
- unless ready?
213
- @config = config
214
- @mutex.synchronize { @resource.signal }
215
- end
216
-
217
- @config = config
218
-
219
- # clearing old optimizely config so that a fresh one is generated on the next api call.
220
- @optimizely_config = nil
221
-
222
- @notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
223
-
224
- @logger.log(Logger::DEBUG, 'Received new datafile and updated config. ' \
225
- "Old revision number: #{previous_revision}. New revision number: #{@config.revision}.")
226
- end
227
-
228
- def polling_interval(polling_interval)
229
- # Sets frequency at which datafile has to be polled and ProjectConfig updated.
230
- #
231
- # polling_interval - Time in seconds after which to update datafile.
232
-
233
- # If valid set given polling interval, default update interval otherwise.
234
-
235
- if polling_interval.nil?
236
- @logger.log(
237
- Logger::DEBUG,
238
- "Polling interval is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
239
- )
240
- @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
241
- return
242
- end
243
-
244
- unless polling_interval.is_a? Numeric
245
- @logger.log(
246
- Logger::ERROR,
247
- "Polling interval '#{polling_interval}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
248
- )
249
- @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
250
- return
251
- end
252
-
253
- unless polling_interval.positive? && polling_interval <= Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT']
254
- @logger.log(
255
- Logger::DEBUG,
256
- "Polling interval '#{polling_interval}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
257
- )
258
- @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
259
- return
260
- end
261
-
262
- @polling_interval = polling_interval
263
- end
264
-
265
- def blocking_timeout(blocking_timeout)
266
- # Sets time in seconds to block the config call until config has been initialized.
267
- #
268
- # blocking_timeout - Time in seconds to block the config call.
269
-
270
- # If valid set given timeout, default blocking_timeout otherwise.
271
-
272
- if blocking_timeout.nil?
273
- @logger.log(
274
- Logger::DEBUG,
275
- "Blocking timeout is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
276
- )
277
- @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
278
- return
279
- end
280
-
281
- unless blocking_timeout.is_a? Integer
282
- @logger.log(
283
- Logger::ERROR,
284
- "Blocking timeout '#{blocking_timeout}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
285
- )
286
- @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
287
- return
288
- end
289
-
290
- unless blocking_timeout.between?(Helpers::Constants::CONFIG_MANAGER['MIN_SECONDS_LIMIT'], Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT'])
291
- @logger.log(
292
- Logger::DEBUG,
293
- "Blocking timeout '#{blocking_timeout}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
294
- )
295
- @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
296
- return
297
- end
298
-
299
- @blocking_timeout = blocking_timeout
300
- end
301
-
302
- def get_datafile_url(sdk_key, url, url_template)
303
- # Determines URL from where to fetch the datafile.
304
- # sdk_key - Key uniquely identifying the datafile.
305
- # url - String representing URL from which to fetch the datafile.
306
- # url_template - String representing template which is filled in with
307
- # SDK key to determine URL from which to fetch the datafile.
308
- # Returns String representing URL to fetch datafile from.
309
- if sdk_key.nil? && url.nil?
310
- error_msg = 'Must provide at least one of sdk_key or url.'
311
- @logger.log(Logger::ERROR, error_msg)
312
- @error_handler.handle_error(InvalidInputsError.new(error_msg))
313
- end
314
-
315
- unless url
316
- url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
317
- begin
318
- return (url_template % sdk_key)
319
- rescue
320
- error_msg = "Invalid url_template #{url_template} provided."
321
- @logger.log(Logger::ERROR, error_msg)
322
- @error_handler.handle_error(InvalidInputsError.new(error_msg))
323
- end
324
- end
325
-
326
- url
327
- end
328
- end
329
- end
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2019-2020, 2022, 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 '../config/datafile_project_config'
19
+ require_relative '../error_handler'
20
+ require_relative '../exceptions'
21
+ require_relative '../helpers/constants'
22
+ require_relative '../helpers/http_utils'
23
+ require_relative '../logger'
24
+ require_relative '../notification_center'
25
+ require_relative '../project_config'
26
+ require_relative '../optimizely_config'
27
+ require_relative 'project_config_manager'
28
+ require_relative 'async_scheduler'
29
+
30
+ require 'json'
31
+
32
+ module Optimizely
33
+ class HTTPProjectConfigManager < ProjectConfigManager
34
+ # Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
35
+
36
+ attr_reader :stopped
37
+
38
+ # Initialize config manager. One of sdk_key or url has to be set to be able to use.
39
+ #
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.
42
+ # polling_interval - Optional floating point number representing time interval in seconds
43
+ # at which to request datafile and set ProjectConfig.
44
+ # blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
45
+ # auto_update - Boolean indicates to run infinitely or only once.
46
+ # start_by_default - Boolean indicates to start by default AsyncScheduler.
47
+ # url - Optional string representing URL from where to fetch the datafile. If set it supersedes the sdk_key.
48
+ # url_template - Optional string template which in conjunction with sdk_key
49
+ # determines URL from where to fetch the datafile.
50
+ # logger - Provides a logger instance.
51
+ # error_handler - Provides a handle_error method to handle exceptions.
52
+ # skip_json_validation - Optional boolean param which allows skipping JSON schema
53
+ # validation upon object invocation. By default JSON schema validation will be performed.
54
+ # datafile_access_token - access token used to fetch private datafiles
55
+ # proxy_config - Optional proxy config instancea to configure making web requests through a proxy server.
56
+ def initialize(
57
+ sdk_key: nil,
58
+ url: nil,
59
+ url_template: nil,
60
+ polling_interval: nil,
61
+ blocking_timeout: nil,
62
+ auto_update: true,
63
+ start_by_default: true,
64
+ datafile: nil,
65
+ logger: nil,
66
+ error_handler: nil,
67
+ skip_json_validation: false,
68
+ notification_center: nil,
69
+ datafile_access_token: nil,
70
+ proxy_config: nil
71
+ )
72
+ super()
73
+ @logger = logger || NoOpLogger.new
74
+ @error_handler = error_handler || NoOpErrorHandler.new
75
+ @access_token = datafile_access_token
76
+ @datafile_url = get_datafile_url(sdk_key, url, url_template)
77
+ @polling_interval = nil
78
+ polling_interval(polling_interval)
79
+ @blocking_timeout = nil
80
+ blocking_timeout(blocking_timeout)
81
+ @last_modified = nil
82
+ @skip_json_validation = skip_json_validation
83
+ @notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
84
+ @optimizely_config = nil
85
+ @config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
86
+ @mutex = Mutex.new
87
+ @resource = ConditionVariable.new
88
+ @async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
89
+ # Start async scheduler in the end to avoid race condition where scheduler executes
90
+ # callback which makes use of variables not yet initialized by the main thread.
91
+ @async_scheduler.start! if start_by_default == true
92
+ @proxy_config = proxy_config
93
+ @stopped = false
94
+ end
95
+
96
+ def ready?
97
+ !@config.nil?
98
+ end
99
+
100
+ def start!
101
+ if @stopped
102
+ @logger.log(Logger::WARN, 'Not starting. Already stopped.')
103
+ return
104
+ end
105
+
106
+ @async_scheduler.start!
107
+ @stopped = false
108
+ end
109
+
110
+ def stop!
111
+ if @stopped
112
+ @logger.log(Logger::WARN, 'Not pausing. Manager has not been started.')
113
+ return
114
+ end
115
+
116
+ @async_scheduler.stop!
117
+ @config = nil
118
+ @stopped = true
119
+ end
120
+
121
+ def config
122
+ # Get Project Config.
123
+
124
+ # if stopped is true, then simply return @config.
125
+ # If the background datafile polling thread is running. and config has been initalized,
126
+ # we simply return @config.
127
+ # If it is not, we wait and block maximum for @blocking_timeout.
128
+ # If thread is not running, we fetch the datafile and update config.
129
+ return @config if @stopped
130
+
131
+ if @async_scheduler.running
132
+ return @config if ready?
133
+
134
+ @mutex.synchronize do
135
+ @resource.wait(@mutex, @blocking_timeout)
136
+ return @config
137
+ end
138
+ end
139
+
140
+ fetch_datafile_config
141
+ @config
142
+ end
143
+
144
+ def optimizely_config
145
+ @optimizely_config = OptimizelyConfig.new(@config).config if @optimizely_config.nil?
146
+
147
+ @optimizely_config
148
+ end
149
+
150
+ private
151
+
152
+ def fetch_datafile_config
153
+ # Fetch datafile, handle response and send notification on config update.
154
+ config = request_config
155
+ return unless config
156
+
157
+ set_config config
158
+ end
159
+
160
+ def request_config
161
+ @logger.log(Logger::DEBUG, "Fetching datafile from #{@datafile_url}")
162
+ headers = {}
163
+ headers['Content-Type'] = 'application/json'
164
+ headers['If-Modified-Since'] = @last_modified if @last_modified
165
+ headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?
166
+
167
+ # Cleaning headers before logging to avoid exposing authorization token
168
+ cleansed_headers = {}
169
+ headers.each { |key, value| cleansed_headers[key] = key == 'Authorization' ? '********' : value }
170
+ @logger.log(Logger::DEBUG, "Datafile request headers: #{cleansed_headers}")
171
+
172
+ begin
173
+ response = Helpers::HttpUtils.make_request(
174
+ @datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT'], @proxy_config
175
+ )
176
+ rescue StandardError => e
177
+ @logger.log(
178
+ Logger::ERROR,
179
+ "Fetching datafile from #{@datafile_url} failed. Error: #{e}"
180
+ )
181
+ return nil
182
+ end
183
+
184
+ response_code = response.code.to_i
185
+ @logger.log(Logger::DEBUG, "Datafile response status code #{response_code}")
186
+
187
+ # Leave datafile and config unchanged if it has not been modified.
188
+ if response.code == '304'
189
+ @logger.log(
190
+ Logger::DEBUG,
191
+ "Not updating config as datafile has not updated since #{@last_modified}."
192
+ )
193
+ return
194
+ end
195
+
196
+ if response_code >= 200 && response_code < 400
197
+ @logger.log(Logger::DEBUG, 'Successfully fetched datafile, generating Project config')
198
+ config = DatafileProjectConfig.create(response.body, @logger, @error_handler, @skip_json_validation)
199
+ @last_modified = response[Helpers::Constants::HTTP_HEADERS['LAST_MODIFIED']]
200
+ @logger.log(Logger::DEBUG, "Saved last modified header value from response: #{@last_modified}.")
201
+ else
202
+ @logger.log(Logger::DEBUG, "Datafile fetch failed, status: #{response.code}, message: #{response.message}")
203
+ end
204
+
205
+ config
206
+ end
207
+
208
+ def set_config(config)
209
+ # Send notification if project config is updated.
210
+ previous_revision = @config.revision if @config
211
+ return if previous_revision == config.revision
212
+
213
+ unless ready?
214
+ @config = config
215
+ @mutex.synchronize { @resource.signal }
216
+ end
217
+
218
+ @config = config
219
+
220
+ # clearing old optimizely config so that a fresh one is generated on the next api call.
221
+ @optimizely_config = nil
222
+
223
+ @notification_center.send_notifications(NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE])
224
+
225
+ @logger.log(Logger::DEBUG, 'Received new datafile and updated config. ' \
226
+ "Old revision number: #{previous_revision}. New revision number: #{@config.revision}.")
227
+ end
228
+
229
+ def polling_interval(polling_interval)
230
+ # Sets frequency at which datafile has to be polled and ProjectConfig updated.
231
+ #
232
+ # polling_interval - Time in seconds after which to update datafile.
233
+
234
+ # If valid set given polling interval, default update interval otherwise.
235
+
236
+ if polling_interval.nil?
237
+ @logger.log(
238
+ Logger::DEBUG,
239
+ "Polling interval is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
240
+ )
241
+ @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
242
+ return
243
+ end
244
+
245
+ unless polling_interval.is_a? Numeric
246
+ @logger.log(
247
+ Logger::ERROR,
248
+ "Polling interval '#{polling_interval}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
249
+ )
250
+ @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
251
+ return
252
+ end
253
+
254
+ unless polling_interval.positive? && polling_interval <= Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT']
255
+ @logger.log(
256
+ Logger::DEBUG,
257
+ "Polling interval '#{polling_interval}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
258
+ )
259
+ @polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
260
+ return
261
+ end
262
+
263
+ @polling_interval = polling_interval
264
+ end
265
+
266
+ def blocking_timeout(blocking_timeout)
267
+ # Sets time in seconds to block the config call until config has been initialized.
268
+ #
269
+ # blocking_timeout - Time in seconds to block the config call.
270
+
271
+ # If valid set given timeout, default blocking_timeout otherwise.
272
+
273
+ if blocking_timeout.nil?
274
+ @logger.log(
275
+ Logger::DEBUG,
276
+ "Blocking timeout is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
277
+ )
278
+ @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
279
+ return
280
+ end
281
+
282
+ unless blocking_timeout.is_a? Integer
283
+ @logger.log(
284
+ Logger::ERROR,
285
+ "Blocking timeout '#{blocking_timeout}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
286
+ )
287
+ @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
288
+ return
289
+ end
290
+
291
+ unless blocking_timeout.between?(Helpers::Constants::CONFIG_MANAGER['MIN_SECONDS_LIMIT'], Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT'])
292
+ @logger.log(
293
+ Logger::DEBUG,
294
+ "Blocking timeout '#{blocking_timeout}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
295
+ )
296
+ @blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
297
+ return
298
+ end
299
+
300
+ @blocking_timeout = blocking_timeout
301
+ end
302
+
303
+ def get_datafile_url(sdk_key, url, url_template)
304
+ # Determines URL from where to fetch the datafile.
305
+ # sdk_key - Key uniquely identifying the datafile.
306
+ # url - String representing URL from which to fetch the datafile.
307
+ # url_template - String representing template which is filled in with
308
+ # SDK key to determine URL from which to fetch the datafile.
309
+ # Returns String representing URL to fetch datafile from.
310
+ if sdk_key.nil? && url.nil?
311
+ error_msg = 'Must provide at least one of sdk_key or url.'
312
+ @logger.log(Logger::ERROR, error_msg)
313
+ @error_handler.handle_error(InvalidInputsError.new(error_msg))
314
+ end
315
+
316
+ unless url
317
+ url_template ||= @access_token.nil? ? Helpers::Constants::CONFIG_MANAGER['DATAFILE_URL_TEMPLATE'] : Helpers::Constants::CONFIG_MANAGER['AUTHENTICATED_DATAFILE_URL_TEMPLATE']
318
+ begin
319
+ return (url_template % sdk_key)
320
+ rescue
321
+ error_msg = "Invalid url_template #{url_template} provided."
322
+ @logger.log(Logger::ERROR, error_msg)
323
+ @error_handler.handle_error(InvalidInputsError.new(error_msg))
324
+ end
325
+ end
326
+
327
+ url
328
+ end
329
+ end
330
+ end