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.
- checksums.yaml +7 -0
- data/lib/rspec/openhab/actions.rb +20 -0
- data/lib/rspec/openhab/api.rb +62 -0
- data/lib/rspec/openhab/core/item_proxy.rb +19 -0
- data/lib/rspec/openhab/core/logger.rb +43 -0
- data/lib/rspec/openhab/core/mocks/bundle_install_support.rb +26 -0
- data/lib/rspec/openhab/core/mocks/bundle_resolver.rb +32 -0
- data/lib/rspec/openhab/core/mocks/event_admin.rb +148 -0
- data/lib/rspec/openhab/core/mocks/persistence_service.rb +144 -0
- data/lib/rspec/openhab/core/mocks/synchronous_executor.rb +56 -0
- data/lib/rspec/openhab/core/mocks/thing_handler.rb +77 -0
- data/lib/rspec/openhab/core/openhab_setup.rb +11 -0
- data/lib/rspec/openhab/dsl/rules/triggers/watch.rb +11 -0
- data/lib/rspec/openhab/dsl/timers/timer.rb +88 -0
- data/lib/rspec/openhab/helpers.rb +249 -0
- data/lib/rspec/openhab/hooks.rb +56 -0
- data/lib/rspec/openhab/jruby.rb +45 -0
- data/lib/rspec/openhab/karaf.rb +733 -0
- data/lib/rspec/openhab/shell.rb +30 -0
- data/lib/rspec/openhab/suspend_rules.rb +56 -0
- data/lib/rspec/openhab/version.rb +7 -0
- data/lib/rspec-openhab-scripting.rb +17 -0
- metadata +232 -0
@@ -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
|