continuent-tools-core 0.0.1

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.
@@ -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