continuent-tools-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,332 @@
1
+ class TungstenInstall
2
+ def initialize(base_path)
3
+ unless self.class.is_installed?(base_path)
4
+ raise "Unable to use #{base_path} because it is not an installed Tungsten directory"
5
+ end
6
+
7
+ @root = TU.cmd_result("cat #{base_path}/.lock")
8
+ TU.debug("Initialize #{self.class.name} from #{@root}")
9
+ @settings = {}
10
+ @topology = nil
11
+
12
+ begin
13
+ @has_tpm = (TU.cmd_result("#{tpm()} query staging") != "")
14
+ if @has_tpm == false
15
+ @has_tpm = (TU.cmd_result("#{tpm()} query dataservices") != "")
16
+ end
17
+ rescue
18
+ @has_tpm = false
19
+ end
20
+
21
+ # Preload settings about this installation
22
+ if use_tpm?()
23
+ # Pull the values from tpm
24
+ settings([
25
+ "user",
26
+ "host_name",
27
+ HOST_ENABLE_REPLICATOR,
28
+ HOST_ENABLE_MANAGER,
29
+ HOST_ENABLE_CONNECTOR,
30
+ REPL_RMI_PORT,
31
+ MGR_RMI_PORT,
32
+ MGR_API,
33
+ MGR_API_PORT,
34
+ MGR_API_ADDRESS,
35
+ "preferred_path"
36
+ ])
37
+ else
38
+ # Read the values from files
39
+ setting(HOST_ENABLE_REPLICATOR, "true")
40
+ setting(HOST_ENABLE_MANAGER, "false")
41
+ setting(HOST_ENABLE_CONNECTOR, "false")
42
+ setting(REPL_RMI_PORT, TU.cmd_result("grep rmi_port #{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/conf/services.properties | grep -v '^#' | awk -F= '{print $2}' | tr -d ' '"))
43
+ setting("host_name", TU.cmd_result("egrep '^replicator.host=' #{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/conf/services.properties | awk -F= '{print $2}'"))
44
+ end
45
+ end
46
+
47
+ def self.is_installed?(base_path)
48
+ if File.exists?("#{base_path}/.manifest") && File.exists?("#{base_path}/.lock")
49
+ return true
50
+ else
51
+ return false
52
+ end
53
+ end
54
+
55
+ def root
56
+ @root
57
+ end
58
+
59
+ def hostname
60
+ setting("host_name")
61
+ end
62
+
63
+ def user
64
+ # Access the array directly to avoid an infinite loop
65
+ @settings["user"]
66
+ end
67
+
68
+ def dataservices
69
+ ds_list = TU.cmd_result("egrep \"^service.name\" #{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/conf/static-* | awk -F \"=\" '{print $2}'").split("\n")
70
+
71
+ if use_tpm?()
72
+ ds_list = ds_list + TU.cmd_result("#{tpm()} query dataservices | grep COMPOSITE | awk -F \" \" '{print $1}'").split("\n")
73
+ end
74
+
75
+ ds_list.uniq()
76
+ end
77
+
78
+ def default_dataservice
79
+ if is_manager?()
80
+ setting("dataservice_name")
81
+ elsif is_replicator?()
82
+ local_services = TU.cmd_result("egrep -l \"^replicator.service.type=local\" #{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/conf/static*").split("\n")
83
+ if local_services.size() == 0
84
+ dataservices().get(0)
85
+ else
86
+ TU.cmd_result("egrep \"^service.name\" #{local_services[0]} | awk -F \"=\" '{print $2}'")
87
+ end
88
+ else
89
+ dataservices()[0]
90
+ end
91
+ end
92
+
93
+ def replication_services
94
+ TU.cmd_result("egrep \"^service.name\" #{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/conf/static-* | awk -F \"=\" '{print $2}'").split("\n")
95
+ end
96
+
97
+ def tpm
98
+ "#{tungsten_sudo_prefix()}#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tools/tpm"
99
+ end
100
+
101
+ def setting(key, v = nil)
102
+ if v == nil
103
+ return settings([key])[key]
104
+ else
105
+ @settings[key] = v
106
+
107
+ return v
108
+ end
109
+ end
110
+
111
+ def settings(keys)
112
+ remaining_keys = keys - @settings.keys()
113
+ if remaining_keys.size() > 0
114
+ if use_tpm?()
115
+ begin
116
+ JSON.parse(TU.cmd_result("#{tpm()} query values #{remaining_keys.join(' ')}")).each{
117
+ |k, v|
118
+ @settings[k] = v
119
+ }
120
+ rescue => e
121
+ TU.exception(e)
122
+ raise "Unable to load tpm values #{keys.join(' ')}"
123
+ end
124
+ else
125
+ TU.debug("Unable to autodetect settings because tpm was not used to install this directory")
126
+ end
127
+ end
128
+
129
+ return_values = {}
130
+ keys.each{
131
+ |k|
132
+ return_values[k] = @settings[k]
133
+ }
134
+ return_values
135
+ end
136
+
137
+ def setting_key(first, second, third = nil)
138
+ if first == CONNECTORS
139
+ "#{first}.#{TU.to_identifier(hostname())}.#{second}"
140
+ elsif first == DATASERVICES
141
+ if third == nil
142
+ raise "Unable to create setting key for #{first}.#{second}"
143
+ end
144
+ "#{first}.#{TU.to_identifier(second)}.#{third}"
145
+ elsif first == HOSTS
146
+ "#{first}.#{TU.to_identifier(hostname())}.#{second}"
147
+ elsif first == MANAGERS
148
+ if third == nil
149
+ raise "Unable to create setting key for #{first}.#{second}"
150
+ end
151
+ "#{first}.#{TU.to_identifier(second)}_#{TU.to_identifier(hostname())}.#{third}"
152
+ elsif first == REPL_SERVICES
153
+ if third == nil
154
+ raise "Unable to create setting key for #{first}.#{second}"
155
+ end
156
+ "#{first}.#{TU.to_identifier(second)}_#{TU.to_identifier(hostname())}.#{third}"
157
+ else
158
+ "#{first}.#{TU.to_identifier(hostname())}.#{second}"
159
+ end
160
+ end
161
+
162
+ def cctrl
163
+ "#{tungsten_sudo_prefix()}#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-manager/bin/cctrl -expert -port #{setting(MGR_RMI_PORT)}"
164
+ end
165
+
166
+ def mgr_api_uri
167
+ if setting(MGR_API_ADDRESS) == "0.0.0.0"
168
+ "#{hostname()}:#{setting(MGR_API_PORT)}"
169
+ else
170
+ "#{setting(MGR_API_ADDRESS)}:#{setting(MGR_API_PORT)}"
171
+ end
172
+ end
173
+
174
+ def status(dataservice = nil)
175
+ if dataservice == nil
176
+ dataservice = default_dataservice()
177
+ end
178
+
179
+ unless dataservices().include?(dataservice)
180
+ raise "Unable to provide a status for #{dataservice} because it is not defined on this host"
181
+ end
182
+
183
+ return TungstenStatus.new(self, dataservice)
184
+ end
185
+
186
+ def topology(dataservice = nil)
187
+ if dataservice == nil
188
+ dataservice = default_dataservice()
189
+ end
190
+
191
+ unless dataservices().include?(dataservice)
192
+ raise "Unable to provide a topology for #{dataservice} because it is not defined on this host"
193
+ end
194
+
195
+ return TungstenTopology.new(self, dataservice)
196
+ end
197
+
198
+ def trepctl(service)
199
+ "#{tungsten_sudo_prefix()}#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/bin/trepctl -port #{setting(REPL_RMI_PORT)} -service #{service}"
200
+ end
201
+
202
+ def trepctl_value(service, key)
203
+ TU.cmd_result("#{trepctl(service)} status | grep #{key} | awk -F: '{print $2}' | tr -d ' '")
204
+ end
205
+
206
+ def trepctl_property(service, key)
207
+ properties = JSON.parse(TU.cmd_result("#{trepctl(service)} properties -filter #{key}"))
208
+ if properties.has_key?(key)
209
+ return properties[key]
210
+ else
211
+ raise "Unable to find a value for #{key} in the output of `trepctl -service #{service} properties`."
212
+ end
213
+ end
214
+
215
+ def thl(service)
216
+ "#{tungsten_sudo_prefix()}#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-replicator/bin/thl -service #{service}"
217
+ end
218
+
219
+ def service_path(component)
220
+ "#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-#{component}/bin/#{component}"
221
+ end
222
+
223
+ def is_running?(component)
224
+ begin
225
+ TU.cmd_result("#{service_path(component)} status")
226
+ return true
227
+ rescue CommandError
228
+ return false
229
+ end
230
+ end
231
+
232
+ def is_replicator?
233
+ (setting(HOST_ENABLE_REPLICATOR) == "true")
234
+ end
235
+
236
+ def is_manager?
237
+ (setting(HOST_ENABLE_MANAGER) == "true")
238
+ end
239
+
240
+ def is_connector?
241
+ (setting(HOST_ENABLE_CONNECTOR) == "true")
242
+ end
243
+
244
+ def is_commercial?
245
+ File.exists?("#{@root}/#{CURRENT_RELEASE_DIRECTORY}/tungsten-manager")
246
+ end
247
+
248
+ def use_tpm?
249
+ @has_tpm
250
+ end
251
+
252
+ def ensure_cctrl(cmd, max_tries = 5)
253
+ i=0
254
+ while (i<max_tries)
255
+ begin
256
+ return TU.cmd_result("echo #{cmd}| #{cctrl}")
257
+ rescue CommandError
258
+ TU.debug(e)
259
+ end
260
+ i+=1
261
+ end
262
+
263
+ raise "Unable to execute '#{cmd}' in cctrl"
264
+ end
265
+
266
+ def inherit_path
267
+ if setting("preferred_path") != ""
268
+ ENV['PATH'] = setting("preferred_path").to_s() + ":" + ENV['PATH']
269
+ end
270
+ end
271
+
272
+ # Build a sudo prefix to run a command as the tungsten system user
273
+ def tungsten_sudo_prefix
274
+ if self.user() == nil || ENV['USER'] == self.user()
275
+ return ""
276
+ else
277
+ return "sudo -u #{self.user()} -n -i "
278
+ end
279
+ end
280
+
281
+ def self.get(path)
282
+ @@instances ||= {}
283
+ unless @@instances.has_key?(path)
284
+ @@instances[path] = TungstenInstall.new(path)
285
+ end
286
+ return @@instances[path]
287
+ end
288
+
289
+ class TungstenTopology
290
+ attr_reader :datasources, :master, :connectors, :dataservices, :type
291
+
292
+ def initialize(install, dataservice)
293
+ @install = install
294
+ @name = dataservice
295
+
296
+ unless @install.use_tpm?()
297
+ raise "Unable to parse the topology for #{@name} from #{@install.hostname()}:#{@install.root()} because tpm was not used for installation"
298
+ end
299
+
300
+ values = @install.settings([
301
+ "dataservices.#{@name}.dataservice_hosts",
302
+ "dataservices.#{@name}.dataservice_master_host",
303
+ "dataservices.#{@name}.dataservice_connectors",
304
+ "dataservices.#{@name}.dataservice_composite_datasources",
305
+ "dataservices.#{@name}.dataservice_topology"
306
+ ])
307
+
308
+ @type = values["dataservices.#{@name}.dataservice_topology"]
309
+ @members = values["dataservices.#{@name}.dataservice_hosts"].to_s().split(",")
310
+ @master = values["dataservices.#{@name}.dataservice_master_host"]
311
+ @connectors = values["dataservices.#{@name}.dataservice_connectors"].to_s().split(",")
312
+ @dataservices = values["dataservices.#{@name}.dataservice_composite_datasources"].to_s().split(",")
313
+ end
314
+
315
+ def is_composite?
316
+ (@dataservices.size() > 0)
317
+ end
318
+
319
+ def to_hash
320
+ {
321
+ :hostname => @install.hostname(),
322
+ :root => @install.root(),
323
+ :is_composite => is_composite?(),
324
+ :type => @type,
325
+ :members => @members,
326
+ :master => @master,
327
+ :connectors => @connectors,
328
+ :dataservices => @dataservices
329
+ }
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,476 @@
1
+ #
2
+ # TUNGSTEN SCALE-OUT STACK
3
+ # Copyright (C) 2009 Continuent, Inc.
4
+ # All rights reserved
5
+ #
6
+
7
+ require "date"
8
+
9
+ # Defines a properties object.
10
+ class Properties
11
+ attr_accessor :props,:use_prompt_handler,:force_json
12
+
13
+ # Initialize with some base values.
14
+ def initialize
15
+ @props = {}
16
+ @in_prompt_handler = {}
17
+ @in_template_value_prompt_handler = {}
18
+ @use_prompt_handler = false
19
+ @prompt_handler = nil
20
+ @force_json = true
21
+ @debug = false
22
+ end
23
+
24
+ def initialize_copy(source)
25
+ super(source)
26
+ @props = Marshal::load(Marshal::dump(@props))
27
+ @in_prompt_handler = {}
28
+ @in_template_value_prompt_handler = {}
29
+ @use_prompt_handler = source.use_prompt_handler
30
+ @prompt_handler = nil
31
+ @force_json = source.force_json
32
+ end
33
+
34
+ # Read properties from a file.
35
+ def load(properties_filename)
36
+ file_contents = ""
37
+
38
+ File.open(properties_filename, 'r') do |file|
39
+ file.read.each_line do |line|
40
+ line.strip!
41
+ unless (line =~ /^#.*/)
42
+ file_contents = file_contents + line
43
+ end
44
+ end
45
+
46
+ begin
47
+ parsed_contents = JSON.parse(file_contents)
48
+ rescue Exception => e
49
+ if file_contents == ""
50
+ parsed_contents = {}
51
+ elsif file_contents[0,1] == "{"
52
+ raise "There was an error parsing the config file: #{e.message}"
53
+ end
54
+ end
55
+
56
+ if parsed_contents && parsed_contents.instance_of?(Hash)
57
+ @props = parsed_contents
58
+ elsif @force_json == true
59
+ raise "There was an error parsing the JSON config file: #{properties_filename}. Try using the migration procedure if you have an old configuration file."
60
+ else
61
+ new_props = {}
62
+
63
+ file.rewind()
64
+ file.read.each_line do |line|
65
+ line.strip!
66
+
67
+ if (line =~ /^([\w\.]+)\[?([\w\.]+)?\]?\s*=\s*(\S.*)/)
68
+ key = $1
69
+ value = $3
70
+
71
+ if $2
72
+ new_props[key] = {} unless new_props[key]
73
+ new_props[key][$2] = value
74
+ else
75
+ new_props[key] = value
76
+ end
77
+ elsif (line =~ /^([\w\.]+)\s*=/)
78
+ key = $1
79
+ value = ""
80
+ new_props[key] = value
81
+ end
82
+ end
83
+
84
+ @props = new_props
85
+ end
86
+
87
+ original_props = @props.dup
88
+
89
+ if original_props != @props
90
+ Configurator.instance.warning("Deprecated keys in the config file were updated")
91
+ end
92
+ end
93
+ end
94
+
95
+ # Read properties from a file.
96
+ def load_and_initialize(properties_filename, keys_module)
97
+ load(properties_filename)
98
+ init(keys_module)
99
+ end
100
+
101
+ def reset
102
+ self.props = {}
103
+ end
104
+
105
+ def debug(v = nil)
106
+ if v != nil
107
+ @debug = v
108
+ end
109
+
110
+ v
111
+ end
112
+
113
+ def import(properties_obj = {})
114
+ if properties_obj.instance_of?(Hash)
115
+ self.props = properties_obj
116
+ elsif properties_obj.instance_of?(Properties)
117
+ self.props = properties_obj.props
118
+ else
119
+ raise "You must pass in a Hash or Properties object to import"
120
+ end
121
+ end
122
+
123
+ # Write properties to a file. We use signal protection to avoid getting
124
+ # interrupted half-way through.
125
+ def store(properties_filename, use_json = true)
126
+ # Protect I/O with trap for Ctrl-C.
127
+ interrupted = false
128
+ old_trap = trap("INT") {
129
+ interrupted = true;
130
+ }
131
+
132
+ # Write.
133
+ File.open(properties_filename, 'w') do |file|
134
+ file.printf "# Tungsten configuration properties\n"
135
+ file.printf "# Date: %s\n", DateTime.now
136
+
137
+ if use_json == false
138
+ @props.sort.each do | key, value |
139
+ file.printf "%s=%s\n", key, value
140
+ end
141
+ else
142
+ file.print self.to_s
143
+ end
144
+ end
145
+
146
+ # Check for interrupt and restore handler.
147
+ if (interrupted)
148
+ puts
149
+ puts ("Configuration interrupted")
150
+ exit 1;
151
+ else
152
+ trap("INT", old_trap);
153
+ end
154
+ end
155
+
156
+ # Return the size of the properties object.
157
+ def size()
158
+ @props.size
159
+ end
160
+
161
+ def to_s
162
+ JSON.pretty_generate(@props)
163
+ end
164
+
165
+ def output()
166
+ TU.output(self.to_s)
167
+ end
168
+
169
+ def force_output()
170
+ TU.force_output(self.to_s)
171
+ end
172
+
173
+ # Fetch a nested hash value
174
+ def getNestedProperty(attrs)
175
+ if attrs.is_a?(String)
176
+ attrs = attrs.split('.')
177
+ end
178
+
179
+ attr_count = attrs.size
180
+ current_val = @props
181
+ for i in 0..(attr_count-1)
182
+ attr_name = attrs[i]
183
+ return current_val[attr_name] if i == (attr_count-1)
184
+ return nil if current_val[attr_name].nil?
185
+ current_val = current_val[attr_name]
186
+ end
187
+
188
+ return nil
189
+ end
190
+
191
+ def setNestedProperty(new_val, attrs)
192
+ attr_count = attrs.size
193
+ current_val = @props
194
+ for i in 0..(attr_count-1)
195
+ attr_name = attrs[i]
196
+ if i == (attr_count-1)
197
+ return setHashProperty(current_val, attr_name, new_val)
198
+ end
199
+ current_val[attr_name] = {} if current_val[attr_name].nil?
200
+ current_val = current_val[attr_name]
201
+ end
202
+ end
203
+
204
+ def setHashProperty(hash, key, value)
205
+ if value == nil || value == []
206
+ return (hash.delete(key))
207
+ else
208
+ if value.is_a?(Hash)
209
+ hash[key] ||= {}
210
+ value.each{|sub_key,sub_value|
211
+ setHashProperty(hash[key], sub_key, sub_value)
212
+ }
213
+ else
214
+ return (hash[key] = value)
215
+ end
216
+ end
217
+ end
218
+
219
+ # Get a property value.
220
+ def getProperty(key, allow_disabled = false)
221
+ if key.is_a?(String)
222
+ key_string = key
223
+ key = key.split('.')
224
+ else
225
+ key_string = key.join('.')
226
+ end
227
+
228
+ value = getNestedProperty(key)
229
+ if value != nil
230
+ return value
231
+ end
232
+
233
+ if usePromptHandler()
234
+ findProperty = lambda do |keys|
235
+ if @in_prompt_handler[key_string] == true
236
+ return nil
237
+ end
238
+
239
+ begin
240
+ @in_prompt_handler[key_string] = true
241
+
242
+ value = getPromptHandler().get_property(keys, allow_disabled)
243
+
244
+ @in_prompt_handler[key_string] = false
245
+ rescue IgnoreError
246
+ @in_prompt_handler[key_string] = false
247
+ rescue => e
248
+ @in_prompt_handler[key_string] = false
249
+ raise e
250
+ end
251
+
252
+ return value
253
+ end
254
+
255
+
256
+ value = findProperty.call(key)
257
+ else
258
+ findProperty = lambda do |keys|
259
+ return getNestedProperty(keys)
260
+ end
261
+ end
262
+
263
+ if value == nil && key.size == 1 && (host = getNestedProperty([DEPLOYMENT_HOST]))
264
+ value = findProperty.call([HOSTS, host, key[0]])
265
+
266
+ if value == nil && key.size == 1
267
+ dataservice = getProperty(DEPLOYMENT_DATASERVICE)
268
+
269
+ if dataservice
270
+ value = findProperty.call([DATASERVICES, dataservice, key[0]])
271
+
272
+ if value == nil
273
+ value = findProperty.call([REPL_SERVICES, dataservice + "_" + host, key[0]])
274
+ end
275
+
276
+ if value == nil
277
+ value = findProperty.call([MANAGERS, dataservice + "_" + host, key[0]])
278
+ end
279
+ end
280
+ end
281
+
282
+ if value == nil
283
+ value = findProperty.call([CONNECTORS, host, key[0]])
284
+ end
285
+
286
+ if value == nil && key.size == 1 && (svc = getNestedProperty([DEPLOYMENT_SERVICE]))
287
+ value = findProperty.call([REPL_SERVICES, svc, key[0]])
288
+ end
289
+ end
290
+
291
+ value
292
+ end
293
+
294
+ # Get the config file value for a property.
295
+ def getTemplateValue(key, transform_values_method = nil)
296
+ if transform_values_method == nil
297
+ transform_values_method = method(:blank_transform_values_method)
298
+ end
299
+
300
+ if key.is_a?(String)
301
+ key_string = key
302
+ key = key.split('.')
303
+ else
304
+ key_string = key.join('.')
305
+ end
306
+
307
+ if usePromptHandler()
308
+ findProperty = lambda do |keys|
309
+ if @in_template_value_prompt_handler[key_string] == true
310
+ return nil
311
+ end
312
+
313
+ begin
314
+ @in_template_value_prompt_handler[key_string] = true
315
+
316
+ value = getPromptHandler().find_template_value(keys, transform_values_method)
317
+
318
+ @in_template_value_prompt_handler[key_string] = false
319
+ rescue IgnoreError
320
+ @in_template_value_prompt_handler[key_string] = false
321
+ rescue => e
322
+ @in_template_value_prompt_handler[key_string] = false
323
+ raise e
324
+ end
325
+
326
+ return value
327
+ end
328
+
329
+ value = findProperty.call(key)
330
+ end
331
+
332
+ if value == nil && key.size == 1 && (host = getNestedProperty([DEPLOYMENT_HOST]))
333
+ value = findProperty.call([HOSTS, host, key[0]])
334
+
335
+ if value == nil && key.size == 1
336
+ dataservice = getNestedProperty([DEPLOYMENT_DATASERVICE])
337
+ if dataservice == nil
338
+ dataservice = getProperty(DEPLOYMENT_DATASERVICE)
339
+ end
340
+
341
+ if dataservice
342
+ value = findProperty.call([DATASERVICES, dataservice, key[0]])
343
+
344
+ if value == nil
345
+ value = findProperty.call([REPL_SERVICES, dataservice + "_" + host, key[0]])
346
+ end
347
+
348
+ if value == nil
349
+ value = findProperty.call([MANAGERS, dataservice + "_" + host, key[0]])
350
+ end
351
+ end
352
+ end
353
+
354
+ if value == nil
355
+ value = findProperty.call([CONNECTORS, host, key[0]])
356
+ end
357
+ end
358
+
359
+ if value == nil && key.size == 1 && (svc = getNestedProperty([DEPLOYMENT_SERVICE]))
360
+ value = findProperty.call([REPL_SERVICES, svc, key[0]])
361
+ end
362
+
363
+ value
364
+ end
365
+
366
+ def blank_transform_values_method(matches)
367
+ ""
368
+ end
369
+
370
+ # Get the property value or return the default if nil
371
+ def getPropertyOr(key, default = "")
372
+ value = getProperty(key)
373
+ if value == nil
374
+ default
375
+ else
376
+ value
377
+ end
378
+ end
379
+
380
+ def getNestedPropertyOr(key, default = "")
381
+ value = getNestedProperty(key)
382
+ if value == nil
383
+ default
384
+ else
385
+ value
386
+ end
387
+ end
388
+
389
+ # Set a property value.
390
+ def setProperty(key, value)
391
+ if key.is_a?(String)
392
+ key = key.split('.')
393
+ end
394
+
395
+ setNestedProperty(value, key)
396
+ end
397
+
398
+ # Set the property to a value only if it is currently unset.
399
+ def setDefault(key, value)
400
+ if key.is_a?(String)
401
+ key = key.split('.')
402
+ end
403
+
404
+ if getNestedProperty(key) == nil
405
+ setNestedProperty(value, key)
406
+ end
407
+ end
408
+
409
+ # Set multiple properties from a delimited string of key value pairs.
410
+ def setPropertiesFromList(list, delimiter)
411
+ keyValuePairs = list.split(delimiter)
412
+ keyValuePairs.each do |pair|
413
+ if pair =~ /^(.*)=(.*)$/
414
+ key = $1
415
+ value = $2
416
+ setProperty(key, value)
417
+ end
418
+ end
419
+ end
420
+
421
+ # Get the underlying hash table.
422
+ def hash()
423
+ @props
424
+ end
425
+
426
+ def usePromptHandler
427
+ return @use_prompt_handler
428
+ end
429
+
430
+ def getPromptHandler
431
+ unless usePromptHandler()
432
+ return nil
433
+ end
434
+
435
+ unless @prompt_handler
436
+ @prompt_handler = ConfigurePromptHandler.new(self)
437
+ end
438
+
439
+ return @prompt_handler
440
+ end
441
+
442
+ def empty?
443
+ (@props.size() == 0)
444
+ end
445
+
446
+ def override(key, value = {})
447
+ if value == nil || value == ""
448
+ value = {}
449
+ end
450
+
451
+ setProperty(key, getNestedPropertyOr(key, {}).merge(value))
452
+ end
453
+
454
+ def include(key, value = {})
455
+ if value == nil || value == ""
456
+ value = {}
457
+ end
458
+
459
+ setProperty(key, value.merge(getNestedPropertyOr(key, {})))
460
+ end
461
+
462
+ def append(key, value = [])
463
+ if value == nil || value == ""
464
+ value = []
465
+ end
466
+
467
+ if ! value.kind_of?(Array)
468
+ value=Array(value)
469
+ end
470
+ currentvalue=getNestedPropertyOr(key, [])
471
+ if ! currentvalue.kind_of?(Array)
472
+ currentvalue=Array(currentvalue)
473
+ end
474
+ setProperty(key, (currentvalue+value).uniq())
475
+ end
476
+ end