rspec-openhab-scripting 1.0.0

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,733 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "jruby"
5
+ require "set"
6
+ require "shellwords"
7
+ require "time"
8
+
9
+ require_relative "jruby"
10
+ require_relative "shell"
11
+
12
+ module RSpec
13
+ module OpenHAB
14
+ class Karaf
15
+ class ScriptExtensionManagerWrapper
16
+ def initialize(manager)
17
+ @manager = manager
18
+ end
19
+
20
+ def get(type)
21
+ @manager.get(type, "jruby")
22
+ end
23
+ end
24
+ private_constant :ScriptExtensionManagerWrapper
25
+
26
+ attr_reader :path
27
+ attr_accessor :include_bindings, :include_jsondb, :private_confdir, :use_root_instance
28
+
29
+ def initialize(path = nil)
30
+ @path = path
31
+ @include_bindings = true
32
+ @include_jsondb = true
33
+ @private_confdir = false
34
+ @use_root_instance = false
35
+ end
36
+
37
+ def launch
38
+ raise ArgumentError, "Path must be supplied if use_root_instance is false" unless path || use_root_instance
39
+
40
+ @path = oh_home if use_root_instance
41
+
42
+ load_boot_jars
43
+ set_env
44
+ set_java_properties
45
+ set_java_properties_from_env
46
+ unless use_root_instance
47
+ redirect_instances
48
+ create_instance
49
+ end
50
+ start_instance
51
+ end
52
+
53
+ private
54
+
55
+ # create a private instances configuration
56
+ def redirect_instances
57
+ # this is normally done directly in bin/karaf with a -D JAVA_OPT
58
+ orig_instances = "#{java.lang.System.get_property("karaf.data")}/tmp/instances"
59
+
60
+ instances_path = "#{path}/instances"
61
+ java.lang.System.set_property("karaf.instances", instances_path)
62
+ FileUtils.mkdir_p(instances_path)
63
+
64
+ new_instance_properties = "#{instances_path}/instance.properties"
65
+ return if File.exist?(new_instance_properties) && File.stat(new_instance_properties).size != 0
66
+
67
+ FileUtils.cp("#{orig_instances}/instance.properties", new_instance_properties)
68
+ end
69
+
70
+ def create_instance
71
+ find_karaf_instance_jar
72
+ # OSGI isn't up yet, so have to create the service directly
73
+ service = org.apache.karaf.instance.core.internal.InstanceServiceImpl.new
74
+ settings = org.apache.karaf.instance.core.InstanceSettings.new(0, 0, 0, path, nil, nil, nil)
75
+ root_instance = service.instances.find(&:root?)
76
+ raise ArgumentError "No root instance found to clone... has OpenHAB run yet?" unless root_instance
77
+
78
+ return if service.get_instance("rspec")
79
+
80
+ service.clone_instance(root_instance.name, "rspec", settings, false)
81
+ ensure
82
+ extra_loaders = ::JRuby.runtime.instance_config.extra_loaders
83
+ loader = extra_loaders.find { |l| l.class_loader == @karaf_instance_loader }
84
+ extra_loaders.remove(loader)
85
+ end
86
+
87
+ def start_instance
88
+ unless use_root_instance
89
+ # these are all from karaf.instances's startup code with
90
+ # the exception of not having data be a subdir
91
+ java.lang.System.set_property("karaf.base", path)
92
+ java.lang.System.set_property("karaf.data", path)
93
+ java.lang.System.set_property("karaf.etc", "#{path}/etc")
94
+ java.lang.System.set_property("karaf.log", "#{path}/logs")
95
+ java.lang.System.set_property("java.io.tmpdir", "#{path}/tmp")
96
+ java.lang.System.set_property("karaf.startLocalConsole", "false")
97
+ java.lang.System.set_property("karaf.startRemoteShell", "false")
98
+ # set in bin/setenv to OPENHAB_USERDATA; need to move it
99
+ java.lang.System.set_property("felix.cm.dir", felix_cm)
100
+ # not handled by karaf instances
101
+ java.lang.System.set_property("openhab.userdata", path)
102
+ java.lang.System.set_property("openhab.logdir", "#{path}/logs")
103
+ end
104
+ cleanup_instance
105
+ # we don't need a shutdown socket
106
+ java.lang.System.set_property("karaf.shutdown.port", "-1")
107
+ # ensure we're not logging to stdout
108
+ java.util.logging.LogManager.log_manager.reset
109
+
110
+ # launch it! (don't use Main.main; it will wait for it to be
111
+ # shut down externally)
112
+ @all_bundles_continue = nil
113
+ @class_loaders = Set.new
114
+ @main = org.apache.karaf.main.Main.new([])
115
+ launch_karaf
116
+ at_exit do
117
+ @main.destroy
118
+ # OSGi/OpenHAB leave a ton of threads around. Kill ourselves ASAP
119
+ code = if $!.nil? || ($!.is_a?(SystemExit) && $!.success?)
120
+ 0
121
+ elsif $!.is_a?(SystemExit)
122
+ $!.status
123
+ else
124
+ puts $!.inspect
125
+ 1
126
+ end
127
+ exit!(code)
128
+ end
129
+
130
+ set_up_bundle_listener
131
+ wait_for_start
132
+ set_jruby_script_presets
133
+ @main
134
+ end
135
+
136
+ def launch_karaf
137
+ # we need to access internals, since we're reproducing much of Main.launch
138
+ klass = org.apache.karaf.main.Main
139
+ klass.field_accessor :classLoader, :activatorManager
140
+ klass.field_writer :framework
141
+ klass.field_reader :LOG
142
+ org.apache.karaf.main.ConfigProperties.field_reader :props, :defaultBundleStartlevel, :karafEtc,
143
+ :defaultStartLevel
144
+ klass.class_eval do
145
+ def send_private(method_name, *args)
146
+ method_name = method_name.to_s
147
+ method = self.class.java_class.declared_methods.find { |m| m.name == method_name }
148
+ method.accessible = true
149
+ method.invoke(self, *args)
150
+ end
151
+
152
+ def launch_simple
153
+ self.config = org.apache.karaf.main.ConfigProperties.new
154
+ config.perform_init
155
+ log4j_config_path = "#{java.lang.System.get_property("karaf.etc")}/org.ops4j.pax.logging.cfg"
156
+ org.apache.karaf.main.util.BootstrapLogManager.set_properties(config.props, log4j_config_path)
157
+ org.apache.karaf.main.util.BootstrapLogManager.configure_logger(self.class.LOG)
158
+
159
+ bundle_dirs = send_private(:getBundleRepos)
160
+ resolver = org.apache.karaf.main.util.SimpleMavenResolver.new(bundle_dirs)
161
+ self.classLoader = send_private(:createClassLoader, resolver)
162
+ factory = send_private(:loadFrameworkFactory, classLoader)
163
+ self.framework = factory.new_framework(config.props)
164
+
165
+ send_private(:setLogger)
166
+
167
+ framework.init
168
+ framework.start
169
+
170
+ sl = framework.adapt(org.osgi.framework.startlevel.FrameworkStartLevel.java_class)
171
+ sl.initial_bundle_start_level = config.defaultBundleStartlevel
172
+
173
+ if framework.bundle_context.bundles.length == 1
174
+ self.class.LOG.info("Installing and starting initial bundles")
175
+ startup_props_file = java.io.File.new(config.karafEtc, self.class::STARTUP_PROPERTIES_FILE_NAME)
176
+ bundles = read_bundles_from_startup_properties(startup_props_file)
177
+ send_private(:installAndStartBundles, resolver, framework.bundle_context, bundles)
178
+ self.class.LOG.info("All initial bundles installed and set to start")
179
+ end
180
+
181
+ server_info = org.apache.karaf.main.ServerInfoImpl.new(args, config)
182
+ framework.bundle_context.register_service(org.apache.karaf.info.ServerInfo.java_class, server_info, nil)
183
+
184
+ self.activatorManager = org.apache.karaf.main.KarafActivatorManager.new(classLoader, framework)
185
+
186
+ # let the caller register services now that the framework is up,
187
+ # but nothing is running yet
188
+ yield framework.bundle_context
189
+
190
+ set_start_level(config.defaultStartLevel)
191
+ end
192
+ end
193
+
194
+ @main.launch_simple do
195
+ # hook up the OSGI class loader manually
196
+ add_class_loader(@main.framework)
197
+
198
+ @framework = @main.framework
199
+ @bundle_context = @main.framework.bundle_context
200
+
201
+ # prevent entirely blocked bundles from starting at all
202
+ @main.framework.bundle_context.bundles.each do |b|
203
+ sl = b.adapt(org.osgi.framework.startlevel.BundleStartLevel.java_class)
204
+ if (start_level = START_LEVEL_OVERRIDES[b.symbolic_name])
205
+ sl.start_level = start_level
206
+ end
207
+ sl.start_level = @main.config.defaultStartLevel + 1 if blocked_bundle?(b)
208
+ end
209
+
210
+ prune_startlevels
211
+
212
+ set_up_service_listener
213
+ # replace event infrastructure with synchronous versions
214
+ wait_for_service("org.osgi.service.event.EventAdmin") do |service|
215
+ next if defined?(OpenHAB::Core::Mocks::EventAdmin) && service.is_a?(OpenHAB::Core::Mocks::EventAdmin)
216
+
217
+ require "rspec/openhab/core/mocks/event_admin"
218
+ ea = OpenHAB::Core::Mocks::EventAdmin.new(@bundle_context)
219
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(service.class)
220
+ # we need to register it as if from the regular eventadmin bundle so other bundles
221
+ # can properly find it
222
+ bundle.bundle_context.register_service(
223
+ org.osgi.service.event.EventAdmin.java_class,
224
+ ea,
225
+ java.util.Hashtable.new(org.osgi.framework.Constants::SERVICE_RANKING => 1.to_java(:int))
226
+ )
227
+ end
228
+ wait_for_service("org.apache.karaf.features.FeaturesService") do |fs|
229
+ require "rspec/openhab/core/mocks/bundle_install_support"
230
+ fs.class.field_reader :installSupport
231
+ field = fs.class.java_class.get_declared_field("installSupport")
232
+ field.accessible = true
233
+ field.set(fs, OpenHAB::Core::Mocks::BundleInstallSupport.new(fs.installSupport, self))
234
+ end
235
+ wait_for_service("org.osgi.service.cm.ConfigurationAdmin") do |ca|
236
+ cfg = ca.get_configuration("org.openhab.addons", nil)
237
+ props = cfg.properties || java.util.Hashtable.new
238
+ # remove all non-binding addons
239
+ props.remove("misc")
240
+ props.remove("package")
241
+ props.remove("persistence")
242
+ props.remove("transformation")
243
+ props.remove("ui")
244
+ props.remove("binding") unless include_bindings
245
+ # except we need jrubyscripting
246
+ props.put("automation", "jrubyscripting")
247
+ cfg.update(props)
248
+
249
+ # configure persistence to use the mock service
250
+ cfg = ca.get_configuration("org.openhab.persistence", nil)
251
+ props = cfg.properties || java.util.Hashtable.new
252
+ props.put("default", "default")
253
+ cfg.update(props)
254
+ end
255
+ wait_for_service("org.openhab.core.automation.RuleManager") do |re|
256
+ require "rspec/openhab/core/mocks/synchronous_executor"
257
+ # overwrite thCallbacks to one that will spy to remove threading
258
+ field = re.class.java_class.declared_field :thCallbacks
259
+ field.accessible = true
260
+ field.set(re, Core::Mocks::CallbacksMap.new)
261
+ re.class.field_accessor :executor
262
+ re.executor = Core::Mocks::SynchronousExecutor.instance
263
+ end
264
+ end
265
+ end
266
+
267
+ # entire bundle trees that are allowed to be installed,
268
+ # but not started
269
+ BLOCKED_BUNDLE_TREES = %w[
270
+ org.apache.karaf.jaas
271
+ org.apache.sshd
272
+ org.eclipse.jetty
273
+ org.ops4j.pax.web
274
+ org.openhab.automation
275
+ org.openhab.binding
276
+ org.openhab.core.io
277
+ org.openhab.io
278
+ org.openhab.transform
279
+ ].freeze
280
+ private_constant :BLOCKED_BUNDLE_TREES
281
+
282
+ ALLOWED_BUNDLES = %w[
283
+ org.openhab.automation.jrubyscripting
284
+ org.openhab.core.io.monitor
285
+ ].freeze
286
+ private_constant :ALLOWED_BUNDLES
287
+
288
+ BLOCKED_COMPONENTS = {
289
+ "org.openhab.core" => %w[
290
+ org.openhab.core.addon.AddonEventFactory
291
+ org.openhab.core.binding.BindingInfoRegistry
292
+ org.openhab.core.binding.i18n.BindingI18nLocalizationService
293
+ org.openhab.core.internal.auth.ManagedUserProvider
294
+ org.openhab.core.internal.auth.UserRegistryImpl
295
+ ].freeze,
296
+ "org.openhab.core.automation.module.script.rulesupport" => %w[
297
+ org.openhab.core.automation.module.script.rulesupport.internal.loader.DefaultScriptFileWatcher
298
+ ].freeze,
299
+ "org.openhab.core.config.core" => %w[
300
+ org.openhab.core.config.core.internal.i18n.I18nConfigOptionsProvider
301
+ org.openhab.core.config.core.status.ConfigStatusService
302
+ org.openhab.core.config.core.status.events.ConfigStatusEventFactory
303
+ ],
304
+ "org.openhab.core.model.script" => %w[
305
+ org.openhab.core.model.script.internal.RuleHumanLanguageInterpreter
306
+ org.openhab.core.model.script.internal.engine.action.VoiceActionService
307
+ org.openhab.core.model.script.jvmmodel.ScriptItemRefresher
308
+ ].freeze,
309
+ "org.openhab.core.thing" => %w[
310
+ org.openhab.core.thing.internal.console.FirmwareUpdateConsoleCommandExtension
311
+ ],
312
+ # the following bundles are blocked completely from starting
313
+ "org.apache.felix.fileinstall" => nil,
314
+ "org.apache.karaf.http.core" => nil,
315
+ "org.apache.karaf.features.command" => nil,
316
+ "org.apache.karaf.shell.commands" => nil,
317
+ "org.apache.karaf.shell.core" => nil,
318
+ "org.apache.karaf.shell.ssh" => nil,
319
+ "org.openhab.core.audio" => nil,
320
+ "org.openhab.core.automation.module.media" => nil,
321
+ "org.openhab.core.config.discovery" => nil,
322
+ "org.openhab.core.model.lsp" => nil,
323
+ "org.openhab.core.model.rule.runtime" => nil,
324
+ "org.openhab.core.model.rule" => nil,
325
+ "org.openhab.core.model.sitemap.runtime" => nil,
326
+ "org.openhab.core.voice" => nil
327
+ }.freeze
328
+ private_constant :BLOCKED_COMPONENTS
329
+
330
+ START_LEVEL_OVERRIDES = {
331
+ }.freeze
332
+ private_constant :START_LEVEL_OVERRIDES
333
+
334
+ def set_up_bundle_listener
335
+ @thing_type_tracker = @config_description_tracker = nil
336
+ wait_for_service("org.openhab.core.thing.binding.ThingTypeProvider",
337
+ filter: "(openhab.scope=core.xml.thing)") do |ttp|
338
+ ttp.class.field_reader :thingTypeTracker
339
+ @thing_type_tracker = ttp.thingTypeTracker
340
+ @thing_type_tracker.class.field_reader :openState
341
+ org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.field_reader :OPENED
342
+ opened = org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.OPENED
343
+ sleep until @thing_type_tracker.openState == opened
344
+ @bundle_context.bundles.each do |bundle|
345
+ @thing_type_tracker.adding_bundle(bundle, nil)
346
+ end
347
+ end
348
+ wait_for_service("org.openhab.core.config.core.ConfigDescriptionProvider",
349
+ filter: "(openhab.scope=core.xml.config)") do |cdp|
350
+ cdp.class.field_reader :configDescriptionTracker
351
+ @config_description_tracker = cdp.configDescriptionTracker
352
+ @config_description_tracker.class.field_reader :openState
353
+ org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.field_reader :OPENED
354
+ opened = org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.OPENED
355
+ sleep until @config_description_tracker.openState == opened
356
+ @bundle_context.bundles.each do |bundle|
357
+ @config_description_tracker.adding_bundle(bundle, nil)
358
+ end
359
+ end
360
+ wait_for_service("org.osgi.service.component.runtime.ServiceComponentRuntime") { |scr| @scr = scr }
361
+ @bundle_context.add_bundle_listener do |event|
362
+ bundle = event.bundle
363
+ bundle_name = bundle.symbolic_name
364
+ sl = bundle.adapt(org.osgi.framework.startlevel.BundleStartLevel.java_class)
365
+ if (start_level = START_LEVEL_OVERRIDES[bundle_name])
366
+ sl.start_level = start_level
367
+ end
368
+ sl.start_level = @main.config.defaultStartLevel + 1 if blocked_bundle?(bundle)
369
+
370
+ if event.type == org.osgi.framework.BundleEvent::RESOLVED
371
+ @thing_type_tracker&.adding_bundle(event.bundle, nil)
372
+ @config_description_tracker&.adding_bundle(event.bundle, nil)
373
+ end
374
+ next unless event.type == org.osgi.framework.BundleEvent::STARTED
375
+
376
+ # just in case
377
+ raise "blocked bundle #{bundle.symbolic_name} started!" if blocked_bundle?(bundle)
378
+
379
+ add_class_loader(bundle)
380
+
381
+ # as soon as we _can_ do this, do it
382
+ link_osgi if bundle.get_resource("org/slf4j/LoggerFactory.class")
383
+
384
+ if @all_bundles_continue && all_bundles_started?
385
+ @all_bundles_continue.call
386
+ @all_bundles_continue = nil
387
+ end
388
+
389
+ if bundle_name == "org.openhab.core"
390
+ require "rspec/openhab/core/mocks/synchronous_executor"
391
+
392
+ org.openhab.core.common.ThreadPoolManager.field_accessor :pools
393
+ org.openhab.core.common.ThreadPoolManager.pools = Core::Mocks::SynchronousExecutorMap.instance
394
+ end
395
+ if bundle_name == "org.openhab.core.thing"
396
+ require "rspec/openhab/core/mocks/bundle_resolver"
397
+ bundle.bundle_context.register_service(
398
+ org.openhab.core.util.BundleResolver.java_class,
399
+ Core::Mocks::BundleResolver.instance,
400
+ java.util.Hashtable.new(org.osgi.framework.Constants::SERVICE_RANKING => 1.to_java(:int))
401
+ )
402
+
403
+ require "rspec/openhab/core/mocks/thing_handler"
404
+ thf = Core::Mocks::ThingHandlerFactory.instance
405
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(org.openhab.core.thing.Thing)
406
+ Core::Mocks::BundleResolver.instance.register_class(thf.class, bundle)
407
+ bundle.bundle_context.register_service(org.openhab.core.thing.binding.ThingHandlerFactory.java_class, thf,
408
+ nil)
409
+ end
410
+ if bundle_name == "org.openhab.core.automation"
411
+ org.openhab.core.automation.internal.TriggerHandlerCallbackImpl.field_accessor :executor
412
+ end
413
+
414
+ next unless BLOCKED_COMPONENTS.key?(bundle_name)
415
+
416
+ components = BLOCKED_COMPONENTS[bundle_name]
417
+ dtos = if components.nil?
418
+ @scr.get_component_description_dt_os(bundle)
419
+ else
420
+ Array(components).map { |component| @scr.get_component_description_dto(bundle, component) }
421
+ end.compact
422
+ dtos.each do |dto|
423
+ @scr.disable_component(dto) if @scr.component_enabled?(dto)
424
+ end
425
+ rescue Exception => e
426
+ puts e.inspect
427
+ puts e.backtrace
428
+ end
429
+ @bundle_context.bundles.each do |bundle|
430
+ next unless bundle.symbolic_name.start_with?("org.openhab.core")
431
+
432
+ add_class_loader(bundle)
433
+ end
434
+ end
435
+
436
+ def set_up_service_listener
437
+ @awaiting_services = {}
438
+ @bundle_context.add_service_listener do |event|
439
+ next unless event.type == org.osgi.framework.ServiceEvent::REGISTERED
440
+
441
+ ref = event.service_reference
442
+ service = nil
443
+
444
+ ref.get_property(org.osgi.framework.Constants::OBJECTCLASS).each do |klass|
445
+ next unless @awaiting_services.key?(klass)
446
+
447
+ @awaiting_services[klass].each do |(block, filter)|
448
+ service ||= @bundle_context.get_service(ref)
449
+ next if filter && !filter.match(ref)
450
+
451
+ service ||= @bundle_context.get_service(ref)
452
+ break unless service
453
+
454
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(service.class)
455
+ add_class_loader(bundle) if bundle
456
+ block.call(service)
457
+ end
458
+ end
459
+ rescue Exception => e
460
+ puts e.inspect
461
+ puts e.backtrace
462
+ end
463
+ end
464
+
465
+ def add_class_loader(bundle)
466
+ return if @class_loaders.include?(bundle.symbolic_name)
467
+
468
+ @class_loaders << bundle.symbolic_name
469
+ ::JRuby.runtime.instance_config.add_loader(JRuby::OSGiBundleClassLoader.new(bundle))
470
+ end
471
+
472
+ def wait_for_service(service_name, filter: nil, &block)
473
+ if defined?(::OpenHAB::Core::OSGI) &&
474
+ (services = ::OpenHAB::Core::OSGI.services(service_name, filter: filter))
475
+ services.each(&block)
476
+ end
477
+
478
+ waiters = @awaiting_services[service_name] ||= []
479
+ waiters << [block, filter && @bundle_context.create_filter(filter)]
480
+ end
481
+
482
+ def wait_for_start
483
+ wait do |continue|
484
+ @all_bundles_continue = continue
485
+ next continue.call if all_bundles_started?
486
+ end
487
+ end
488
+
489
+ def all_bundles_started?
490
+ has_core = false
491
+ result = @bundle_context.bundles.all? do |b|
492
+ has_core = true if b.symbolic_name == "org.openhab.core"
493
+ b.state == org.osgi.framework.Bundle::ACTIVE ||
494
+ blocked_bundle?(b)
495
+ end
496
+
497
+ result && has_core
498
+ end
499
+
500
+ def blocked_bundle?(bundle)
501
+ return false if ALLOWED_BUNDLES.include?(bundle.symbolic_name)
502
+
503
+ BLOCKED_COMPONENTS.fetch(bundle.symbolic_name, false).nil? ||
504
+ BLOCKED_BUNDLE_TREES.any? { |tree| bundle.symbolic_name.start_with?(tree) } ||
505
+ bundle.fragment?
506
+ end
507
+
508
+ def wait
509
+ mutex = Mutex.new
510
+ cond = ConditionVariable.new
511
+ skip_wait = false
512
+
513
+ continue = lambda do
514
+ # if continue was called synchronously, we can just return
515
+ next skip_wait = true if mutex.owned?
516
+
517
+ mutex.synchronize { cond.signal }
518
+ end
519
+ mutex.synchronize do
520
+ yield continue
521
+ cond.wait(mutex) unless skip_wait
522
+ end
523
+ end
524
+
525
+ def link_osgi
526
+ ::OpenHAB::Core::OSGI.instance_variable_set(:@bundle, @framework) if require "openhab/core/osgi"
527
+ end
528
+
529
+ # import global variables and constants that openhab-scripting gem expects,
530
+ # since we're going to be running it in this same VM
531
+ def set_jruby_script_presets
532
+ wait_for_service("org.openhab.core.automation.module.script.internal.ScriptExtensionManager") do |sem|
533
+ # since we're not created by the ScriptEngineManager, this never gets set; manually set it
534
+ $se = $scriptExtension = ScriptExtensionManagerWrapper.new(sem)
535
+ scope_values = sem.find_default_presets("rspec")
536
+ scope_values = scope_values.entry_set
537
+ jrubyscripting = ::OpenHAB::Core::OSGI.services(
538
+ "org.openhab.core.automation.module.script.ScriptEngineFactory",
539
+ filter: "(service.pid=org.openhab.automation.jrubyscripting)"
540
+ ).first
541
+
542
+ %w[mapInstancePresets mapGlobalPresets].each do |method_name|
543
+ method = jrubyscripting.class.java_class.get_declared_method(method_name, java.util.Map::Entry.java_class)
544
+
545
+ method.accessible = true
546
+ scope_values = scope_values.map { |e| method.invoke(nil, e) }
547
+ end
548
+
549
+ scope_values.each do |entry|
550
+ key = entry.key
551
+ value = entry.value
552
+ # convert Java classes to Ruby classes
553
+ value = value.ruby_class if value.is_a?(java.lang.Class) # rubocop:disable Lint/UselessAssignment
554
+ # constants need to go into the global namespace
555
+ key = "::#{key}" if ("A".."Z").cover?(key[0])
556
+ eval("#{key} = value unless defined?(#{key})", nil, __FILE__, __LINE__) # rubocop:disable Security/Eval
557
+ end
558
+ end
559
+ end
560
+
561
+ # instance isn't part of the boot jars, but we need access to it
562
+ # before we boot karaf in order to create the clone, so we have to
563
+ # find it manually
564
+ def find_karaf_instance_jar
565
+ resolver = org.apache.karaf.main.util.SimpleMavenResolver.new([java.io.File.new("#{oh_runtime}/system")])
566
+ slf4j_version = find_maven_jar_version("org.ops4j.pax.logging", "pax-logging-api")
567
+ slf4j = resolver.resolve(java.net.URI.new("mvn:org.ops4j.pax.logging/pax-logging-api/#{slf4j_version}"))
568
+ karaf_version = find_jar_version("#{oh_runtime}/lib/boot", "org.apache.karaf.main")
569
+ karaf_instance = resolver.resolve(
570
+ java.net.URI.new(
571
+ "mvn:org.apache.karaf.instance/org.apache.karaf.instance.core/#{karaf_version}"
572
+ )
573
+ )
574
+ @karaf_instance_loader = java.net.URLClassLoader.new(
575
+ [slf4j.to_url, karaf_instance.to_url].to_java(java.net.URL), ::JRuby.runtime.jruby_class_loader
576
+ )
577
+ ::JRuby.runtime.instance_config.add_loader(@karaf_instance_loader)
578
+ end
579
+
580
+ def find_maven_jar_version(group, bundle)
581
+ Dir["#{oh_runtime}/system/#{group.tr(".", "/")}/#{bundle}/*"].map { |version| version.split("/").last }.max
582
+ end
583
+
584
+ def find_jar_version(path, bundle)
585
+ prefix = "#{path}/#{bundle}-"
586
+ Dir["#{prefix}*.jar"].map { |jar| jar.split("-", 2).last[0...-4] }.max
587
+ end
588
+
589
+ def load_boot_jars
590
+ (Dir["#{oh_runtime}/lib/boot/*.jar"] +
591
+ Dir["#{oh_runtime}/lib/endorsed/*.jar"] +
592
+ Dir["#{oh_runtime}/lib/jdk9plus/*.jar"]).each do |jar|
593
+ require jar
594
+ end
595
+ end
596
+
597
+ def set_env
598
+ ENV["DIRNAME"] = "#{oh_runtime}/bin"
599
+ ENV["KARAF_HOME"] = oh_runtime
600
+ if private_confdir
601
+ ENV["OPENHAB_CONF"] = "#{path}/conf"
602
+ FileUtils.mkdir_p([
603
+ "#{path}/conf/items",
604
+ "#{path}/conf/things",
605
+ "#{path}/conf/scripts",
606
+ "#{path}/conf/rules",
607
+ "#{path}/conf/persistence",
608
+ "#{path}/conf/sitemaps",
609
+ "#{path}/conf/transform"
610
+ ])
611
+ end
612
+ Shell.source_env_from("#{oh_runtime}/bin/setenv")
613
+ end
614
+
615
+ def set_java_properties
616
+ [ENV.fetch("JAVA_OPTS", nil), ENV.fetch("EXTRA_JAVA_OPTS", nil)].compact.each do |java_opts|
617
+ Shellwords.split(java_opts).each do |arg|
618
+ next unless arg.start_with?("-D")
619
+
620
+ k, v = arg[2..].split("=", 2)
621
+ java.lang.System.set_property(k, v)
622
+ end
623
+ end
624
+ end
625
+
626
+ # we can't set Java ENV directly, so we have to try and set some things
627
+ # as system properties
628
+ def set_java_properties_from_env
629
+ ENV.each do |(k, v)|
630
+ next unless k.match?(/^(?:KARAF|OPENHAB)_/)
631
+
632
+ prop = k.downcase.tr("_", ".")
633
+ next unless java.lang.System.get_property(prop).nil?
634
+
635
+ java.lang.System.set_property(prop, v)
636
+ end
637
+ end
638
+
639
+ def oh_home
640
+ @oh_home ||= ENV.fetch("OPENHAB_HOME", "/usr/share/openhab")
641
+ end
642
+
643
+ def oh_runtime
644
+ @oh_runtime ||= ENV.fetch("OPENHAB_RUNTIME", "#{oh_home}/runtime")
645
+ end
646
+
647
+ def oh_conf
648
+ @oh_conf ||= ENV.fetch("OPENHAB_CONF")
649
+ end
650
+
651
+ def oh_userdata
652
+ @oh_userdata ||= java.lang.System.get_property("openhab.userdata")
653
+ end
654
+
655
+ def felix_cm
656
+ @felix_cm ||= use_root_instance ? ENV.fetch("OPENHAB_USERDATA") : "#{path}/config"
657
+ end
658
+
659
+ def cleanup_instance
660
+ cleanup_clone
661
+ minimize_installed_features
662
+ end
663
+
664
+ def cleanup_clone
665
+ FileUtils.rm_rf(["#{oh_userdata}/cache",
666
+ "#{oh_userdata}/jsondb/backup",
667
+ "#{oh_userdata}/marketplace",
668
+ "#{oh_userdata}/logs/*",
669
+ "#{oh_userdata}/tmp/*",
670
+ "#{oh_userdata}/jsondb/org.openhab.marketplace.json",
671
+ "#{oh_userdata}/jsondb/org.openhab.jsonaddonservice.json",
672
+ "#{felix_cm}/org/openhab/jsonaddonservice.config"])
673
+ FileUtils.rm_rf("#{oh_userdata}/jsondb") unless include_jsondb
674
+ end
675
+
676
+ def prune_startlevels
677
+ config_file = java.lang.System.get_property("openhab.servicecfg")
678
+ return unless File.exist?(config_file)
679
+
680
+ startlevels = File.read(config_file)
681
+ startlevels.sub!(",rules:refresh,rules:dslprovider", "")
682
+
683
+ target_file = "#{oh_userdata}/etc/services.cfg"
684
+ target_file_contents = File.read(target_file) if File.exist?(target_file)
685
+ File.write(target_file, startlevels) unless target_file_contents == startlevels
686
+ java.lang.System.set_property("openhab.servicecfg", target_file)
687
+ end
688
+
689
+ def minimize_installed_features
690
+ # cuts down openhab-runtime-base significantly, makes sure
691
+ # openhab-runtime-ui doesn't get installed (from profile.cfg),
692
+ # double-makes-sure no addons get installed, and marks several
693
+ # bundles to not actually start, even though they must still be
694
+ # installed to meet dependencies
695
+ version = find_maven_jar_version("org.openhab.core.bundles", "org.openhab.core")
696
+ File.write("#{oh_userdata}/etc/org.apache.karaf.features.xml", <<~XML)
697
+ <?xml version="1.0" encoding="UTF-8"?>
698
+ <featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0" xmlns:f="http://karaf.apache.org/xmlns/features/v1.6.0">
699
+ <blacklistedFeatures>
700
+ <feature>openhab-runtime-ui</feature>
701
+ <feature>openhab-core-ui*</feature>
702
+ <feature>openhab-misc-*</feature>
703
+ <feature>openhab-persistence-*</feature>
704
+ <feature>openhab-package-standard</feature>
705
+ <feature>openhab-ui-*</feature>
706
+ <feature>openhab-voice-*</feature>
707
+ </blacklistedFeatures>
708
+ <featureReplacements>
709
+ <replacement mode="replace">
710
+ <feature name="openhab-runtime-base" version="#{version.sub("-", ".")}">
711
+ <f:feature>openhab-core-base</f:feature>
712
+ <f:feature>openhab-core-automation-module-script</f:feature>
713
+ <f:feature>openhab-core-automation-module-script-rulesupport</f:feature>
714
+ <f:feature>openhab-core-automation-module-media</f:feature>
715
+ <f:feature>openhab-core-model-item</f:feature>
716
+ <f:feature>openhab-core-model-persistence</f:feature>
717
+ <f:feature>openhab-core-model-rule</f:feature>
718
+ <f:feature>openhab-core-model-script</f:feature>
719
+ <f:feature>openhab-core-model-sitemap</f:feature>
720
+ <f:feature>openhab-core-model-thing</f:feature>
721
+ <f:feature>openhab-core-storage-json</f:feature>
722
+ <f:feature>openhab-automation-jrubyscripting</f:feature>
723
+ <f:feature>openhab-transport-http</f:feature>
724
+ <f:bundle>mvn:org.openhab.core.bundles/org.openhab.core.karaf/#{version}</f:bundle>
725
+ </feature>
726
+ </replacement>
727
+ </featureReplacements>
728
+ </featuresProcessing>
729
+ XML
730
+ end
731
+ end
732
+ end
733
+ end