rspec-openhab-scripting 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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