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