openhab-jrubyscripting 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,811 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "set"
5
+ require "shellwords"
6
+ require "time"
7
+
8
+ require_relative "jruby"
9
+ require_relative "shell"
10
+
11
+ module OpenHAB
12
+ module RSpec
13
+ # @!visibility private
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?(Mocks::EventAdmin) && service.is_a?(Mocks::EventAdmin)
216
+
217
+ require_relative "mocks/event_admin"
218
+ ea = 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_relative "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, Mocks::BundleInstallSupport.new(fs.installSupport, self))
234
+ end
235
+ wait_for_service("org.osgi.service.cm.ConfigurationAdmin") do |ca|
236
+ # register a listener, so that we can know if the Start Level Service is busted
237
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(ca.class)
238
+ listener = org.osgi.service.cm.ConfigurationListener.impl do |_method, event|
239
+ next unless event.type == org.osgi.service.cm.ConfigurationEvent::CM_UPDATED
240
+ next unless event.pid == "org.openhab.startlevel"
241
+
242
+ # have to wait for the StartLevelService itself to process this event
243
+ Thread.new do
244
+ sleep 1
245
+ reset_start_level_service
246
+ end
247
+ end
248
+ bundle.bundle_context.register_service(org.osgi.service.cm.ConfigurationListener.java_class,
249
+ listener,
250
+ nil)
251
+
252
+ cfg = ca.get_configuration("org.openhab.addons", nil)
253
+ props = cfg.properties || java.util.Hashtable.new
254
+ # remove all non-binding addons
255
+ props.remove("misc")
256
+ props.remove("package")
257
+ props.remove("persistence")
258
+ props.remove("transformation")
259
+ props.remove("ui")
260
+ props.remove("binding") unless include_bindings
261
+ # except we need jrubyscripting
262
+ props.put("automation", "jrubyscripting")
263
+ cfg.update(props)
264
+
265
+ # configure persistence to use the mock service
266
+ cfg = ca.get_configuration("org.openhab.persistence", nil)
267
+ props = cfg.properties || java.util.Hashtable.new
268
+ props.put("default", "default")
269
+ cfg.update(props)
270
+ end
271
+ wait_for_service("org.openhab.core.automation.RuleManager") do |re|
272
+ require_relative "mocks/synchronous_executor"
273
+ # overwrite thCallbacks to one that will spy to remove threading
274
+ field = re.class.java_class.declared_field :thCallbacks
275
+ field.accessible = true
276
+ field.set(re, Mocks::CallbacksMap.new)
277
+ re.class.field_accessor :executor
278
+ re.executor = Mocks::SynchronousExecutor.instance
279
+ end
280
+ wait_for_service("org.openhab.core.thing.internal.CommunicationManager") do |cm|
281
+ require_relative "mocks/safe_caller"
282
+ field = cm.class.java_class.declared_field :safeCaller
283
+ field.accessible = true
284
+ field.set(cm, Mocks::SafeCaller.instance)
285
+ end
286
+ end
287
+ end
288
+
289
+ # entire bundle trees that are allowed to be installed,
290
+ # but not started
291
+ BLOCKED_BUNDLE_TREES = %w[
292
+ org.apache.karaf.jaas
293
+ org.apache.sshd
294
+ org.eclipse.jetty
295
+ org.ops4j.pax.web
296
+ org.openhab.automation
297
+ org.openhab.binding
298
+ org.openhab.core.io
299
+ org.openhab.io
300
+ org.openhab.transform
301
+ ].freeze
302
+ private_constant :BLOCKED_BUNDLE_TREES
303
+
304
+ ALLOWED_BUNDLES = %w[
305
+ org.openhab.automation.jrubyscripting
306
+ org.openhab.core.io.monitor
307
+ ].freeze
308
+ private_constant :ALLOWED_BUNDLES
309
+
310
+ BLOCKED_COMPONENTS = {
311
+ "org.openhab.core" => %w[
312
+ org.openhab.core.addon.AddonEventFactory
313
+ org.openhab.core.binding.i18n.BindingI18nLocalizationService
314
+ org.openhab.core.internal.auth.ManagedUserProvider
315
+ org.openhab.core.internal.auth.UserRegistryImpl
316
+ ].freeze,
317
+ "org.openhab.core.automation.module.script.rulesupport" => %w[
318
+ org.openhab.core.automation.module.script.rulesupport.internal.loader.DefaultScriptFileWatcher
319
+ ].freeze,
320
+ "org.openhab.core.config.core" => %w[
321
+ org.openhab.core.config.core.internal.i18n.I18nConfigOptionsProvider
322
+ org.openhab.core.config.core.status.ConfigStatusService
323
+ org.openhab.core.config.core.status.events.ConfigStatusEventFactory
324
+ ],
325
+ "org.openhab.core.model.script" => %w[
326
+ org.openhab.core.model.script.internal.RuleHumanLanguageInterpreter
327
+ org.openhab.core.model.script.internal.engine.action.VoiceActionService
328
+ org.openhab.core.model.script.jvmmodel.ScriptItemRefresher
329
+ ].freeze,
330
+ "org.openhab.core.thing" => %w[
331
+ org.openhab.core.thing.internal.console.FirmwareUpdateConsoleCommandExtension
332
+ ],
333
+ # the following bundles are blocked completely from starting
334
+ "org.apache.karaf.http.core" => nil,
335
+ "org.apache.karaf.features.command" => nil,
336
+ "org.apache.karaf.shell.commands" => nil,
337
+ "org.apache.karaf.shell.core" => nil,
338
+ "org.apache.karaf.shell.ssh" => nil,
339
+ "org.openhab.core.audio" => nil,
340
+ "org.openhab.core.automation.module.media" => nil,
341
+ "org.openhab.core.config.discovery" => nil,
342
+ "org.openhab.core.model.lsp" => nil,
343
+ "org.openhab.core.model.rule.runtime" => nil,
344
+ "org.openhab.core.model.rule" => nil,
345
+ "org.openhab.core.model.sitemap.runtime" => nil,
346
+ "org.openhab.core.voice" => nil
347
+ }.freeze
348
+ private_constant :BLOCKED_COMPONENTS
349
+
350
+ START_LEVEL_OVERRIDES = {
351
+ }.freeze
352
+ private_constant :START_LEVEL_OVERRIDES
353
+
354
+ def set_up_bundle_listener
355
+ @thing_type_tracker = @config_description_tracker = nil
356
+ wait_for_service("org.openhab.core.thing.binding.ThingTypeProvider",
357
+ filter: "(openhab.scope=core.xml.thing)") do |ttp|
358
+ ttp.class.field_reader :thingTypeTracker
359
+ @thing_type_tracker = ttp.thingTypeTracker
360
+ @thing_type_tracker.class.field_reader :openState
361
+ org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.field_reader :OPENED
362
+ opened = org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.OPENED
363
+ sleep until @thing_type_tracker.openState == opened
364
+ @bundle_context.bundles.each do |bundle|
365
+ @thing_type_tracker.adding_bundle(bundle, nil)
366
+ end
367
+ end
368
+ wait_for_service("org.openhab.core.config.core.ConfigDescriptionProvider",
369
+ filter: "(openhab.scope=core.xml.config)") do |cdp|
370
+ cdp.class.field_reader :configDescriptionTracker
371
+ @config_description_tracker = cdp.configDescriptionTracker
372
+ @config_description_tracker.class.field_reader :openState
373
+ org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.field_reader :OPENED
374
+ opened = org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker::OpenState.OPENED
375
+ sleep until @config_description_tracker.openState == opened
376
+ @bundle_context.bundles.each do |bundle|
377
+ @config_description_tracker.adding_bundle(bundle, nil)
378
+ end
379
+ end
380
+ wait_for_service("org.osgi.service.component.runtime.ServiceComponentRuntime") { |scr| @scr = scr }
381
+ @bundle_context.add_bundle_listener do |event|
382
+ bundle = event.bundle
383
+ bundle_name = bundle.symbolic_name
384
+ sl = bundle.adapt(org.osgi.framework.startlevel.BundleStartLevel.java_class)
385
+ if (start_level = START_LEVEL_OVERRIDES[bundle_name])
386
+ sl.start_level = start_level
387
+ end
388
+ sl.start_level = @main.config.defaultStartLevel + 1 if blocked_bundle?(bundle)
389
+
390
+ if event.type == org.osgi.framework.BundleEvent::RESOLVED
391
+ @thing_type_tracker&.adding_bundle(event.bundle, nil)
392
+ @config_description_tracker&.adding_bundle(event.bundle, nil)
393
+ end
394
+ next unless event.type == org.osgi.framework.BundleEvent::STARTED
395
+
396
+ # just in case
397
+ raise "blocked bundle #{bundle.symbolic_name} started!" if blocked_bundle?(bundle)
398
+
399
+ add_class_loader(bundle)
400
+
401
+ # as soon as we _can_ do this, do it
402
+ link_osgi if bundle.get_resource("org/slf4j/LoggerFactory.class")
403
+
404
+ if @all_bundles_continue && all_bundles_started?
405
+ @all_bundles_continue.call
406
+ @all_bundles_continue = nil
407
+ end
408
+
409
+ if bundle_name == "org.openhab.core"
410
+ require_relative "mocks/synchronous_executor"
411
+
412
+ org.openhab.core.common.ThreadPoolManager.field_accessor :pools
413
+ org.openhab.core.common.ThreadPoolManager.pools = Mocks::SynchronousExecutorMap.instance
414
+ end
415
+ if bundle_name == "org.openhab.core.thing"
416
+ require_relative "mocks/bundle_resolver"
417
+ bundle.bundle_context.register_service(
418
+ org.openhab.core.util.BundleResolver.java_class,
419
+ Mocks::BundleResolver.instance,
420
+ java.util.Hashtable.new(org.osgi.framework.Constants::SERVICE_RANKING => 1.to_java(:int))
421
+ )
422
+
423
+ require_relative "mocks/thing_handler"
424
+ thf = Mocks::ThingHandlerFactory.instance
425
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(org.openhab.core.thing.Thing)
426
+ Mocks::BundleResolver.instance.register_class(thf.class, bundle)
427
+ bundle.bundle_context.register_service(org.openhab.core.thing.binding.ThingHandlerFactory.java_class, thf,
428
+ nil)
429
+ end
430
+ if bundle_name == "org.openhab.core.automation"
431
+ org.openhab.core.automation.internal.TriggerHandlerCallbackImpl.field_accessor :executor
432
+ end
433
+
434
+ next unless BLOCKED_COMPONENTS.key?(bundle_name)
435
+
436
+ components = BLOCKED_COMPONENTS[bundle_name]
437
+ dtos = if components.nil?
438
+ @scr.get_component_description_dt_os(bundle)
439
+ else
440
+ Array(components).map { |component| @scr.get_component_description_dto(bundle, component) }
441
+ end.compact
442
+ dtos.each do |dto|
443
+ @scr.disable_component(dto) if @scr.component_enabled?(dto)
444
+ end
445
+ rescue Exception => e
446
+ puts e.inspect
447
+ puts e.backtrace
448
+ end
449
+ @bundle_context.bundles.each do |bundle|
450
+ next unless bundle.symbolic_name.start_with?("org.openhab.core")
451
+
452
+ add_class_loader(bundle)
453
+ end
454
+ end
455
+
456
+ def set_up_service_listener
457
+ @awaiting_services = {}
458
+ @bundle_context.add_service_listener do |event|
459
+ next unless event.type == org.osgi.framework.ServiceEvent::REGISTERED
460
+
461
+ ref = event.service_reference
462
+ service = nil
463
+
464
+ ref.get_property(org.osgi.framework.Constants::OBJECTCLASS).each do |klass|
465
+ next unless @awaiting_services.key?(klass)
466
+
467
+ @awaiting_services[klass].each do |(block, filter)|
468
+ service ||= @bundle_context.get_service(ref)
469
+ next if filter && !filter.match(ref)
470
+
471
+ service ||= @bundle_context.get_service(ref)
472
+ break unless service
473
+
474
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(service.class)
475
+ add_class_loader(bundle) if bundle
476
+ block.call(service)
477
+ end
478
+ end
479
+ rescue Exception => e
480
+ puts e.inspect
481
+ puts e.backtrace
482
+ end
483
+ end
484
+
485
+ def add_class_loader(bundle)
486
+ return if @class_loaders.include?(bundle.symbolic_name)
487
+
488
+ @class_loaders << bundle.symbolic_name
489
+ ::JRuby.runtime.instance_config.add_loader(JRuby::OSGiBundleClassLoader.new(bundle))
490
+ end
491
+
492
+ def wait_for_service(service_name, filter: nil, &block)
493
+ if defined?(OSGi) &&
494
+ (services = OSGi.services(service_name, filter: filter))
495
+ services.each(&block)
496
+ end
497
+
498
+ waiters = @awaiting_services[service_name] ||= []
499
+ waiters << [block, filter && @bundle_context.create_filter(filter)]
500
+ end
501
+
502
+ def wait_for_start
503
+ wait do |continue|
504
+ @all_bundles_continue = continue
505
+ next continue.call if all_bundles_started?
506
+ end
507
+ end
508
+
509
+ def all_bundles_started?
510
+ has_core = false
511
+ result = @bundle_context.bundles.all? do |b|
512
+ has_core = true if b.symbolic_name == "org.openhab.core"
513
+ b.state == org.osgi.framework.Bundle::ACTIVE ||
514
+ blocked_bundle?(b)
515
+ end
516
+
517
+ result && has_core
518
+ end
519
+
520
+ def blocked_bundle?(bundle)
521
+ return false if ALLOWED_BUNDLES.include?(bundle.symbolic_name)
522
+
523
+ BLOCKED_COMPONENTS.fetch(bundle.symbolic_name, false).nil? ||
524
+ BLOCKED_BUNDLE_TREES.any? { |tree| bundle.symbolic_name.start_with?(tree) } ||
525
+ bundle.fragment?
526
+ end
527
+
528
+ def wait
529
+ mutex = Mutex.new
530
+ cond = ConditionVariable.new
531
+ skip_wait = false
532
+
533
+ continue = lambda do
534
+ # if continue was called synchronously, we can just return
535
+ next skip_wait = true if mutex.owned?
536
+
537
+ mutex.synchronize { cond.signal }
538
+ end
539
+ mutex.synchronize do
540
+ yield continue
541
+ cond.wait(mutex) unless skip_wait
542
+ end
543
+ end
544
+
545
+ def link_osgi
546
+ OSGi.instance_variable_set(:@bundle, @framework) if require "openhab/osgi"
547
+ end
548
+
549
+ # import global variables and constants that the DSL expects,
550
+ # since we're going to be running it in this same VM
551
+ def set_jruby_script_presets
552
+ wait_for_service("org.openhab.core.automation.module.script.ScriptEngineFactory",
553
+ filter: "(service.config.description.uri=automation:jruby)") do |jrubyscripting|
554
+ # "org.openhab.core.automation.module.script.internal.ScriptExtensionManager") do |sem|
555
+ sem = OSGi.service(
556
+ "org.openhab.core.automation.module.script.internal.ScriptExtensionManager"
557
+ )
558
+ # since we're not created by the ScriptEngineManager, this never gets set; manually set it
559
+ $se = $scriptExtension = ScriptExtensionManagerWrapper.new(sem)
560
+ scope_values = sem.find_default_presets("rspec")
561
+ scope_values = scope_values.entry_set
562
+
563
+ %w[mapInstancePresets mapGlobalPresets].each do |method_name|
564
+ method = jrubyscripting.class.java_class.get_declared_method(method_name, java.util.Map::Entry.java_class)
565
+
566
+ method.accessible = true
567
+ scope_values = scope_values.map { |e| method.invoke(nil, e) }
568
+ end
569
+
570
+ scope_values.each do |entry|
571
+ key = entry.key
572
+ value = entry.value
573
+ # convert Java classes to Ruby classes
574
+ value = value.ruby_class if value.is_a?(java.lang.Class) # rubocop:disable Lint/UselessAssignment
575
+ # constants need to go into the global namespace
576
+ key = "::#{key}" if ("A".."Z").cover?(key[0])
577
+ eval("#{key} = value unless defined?(#{key})", nil, __FILE__, __LINE__) # rubocop:disable Security/Eval
578
+ end
579
+ end
580
+ end
581
+
582
+ # instance isn't part of the boot jars, but we need access to it
583
+ # before we boot karaf in order to create the clone, so we have to
584
+ # find it manually
585
+ def find_karaf_instance_jar
586
+ resolver = org.apache.karaf.main.util.SimpleMavenResolver.new([java.io.File.new("#{oh_runtime}/system")])
587
+ slf4j_version = find_maven_jar_version("org.ops4j.pax.logging", "pax-logging-api")
588
+ slf4j = resolver.resolve(java.net.URI.new("mvn:org.ops4j.pax.logging/pax-logging-api/#{slf4j_version}"))
589
+ karaf_version = find_jar_version("#{oh_runtime}/lib/boot", "org.apache.karaf.main")
590
+ karaf_instance = resolver.resolve(
591
+ java.net.URI.new(
592
+ "mvn:org.apache.karaf.instance/org.apache.karaf.instance.core/#{karaf_version}"
593
+ )
594
+ )
595
+ @karaf_instance_loader = java.net.URLClassLoader.new(
596
+ [slf4j.to_url, karaf_instance.to_url].to_java(java.net.URL), ::JRuby.runtime.jruby_class_loader
597
+ )
598
+ ::JRuby.runtime.instance_config.add_loader(@karaf_instance_loader)
599
+ end
600
+
601
+ def find_maven_jar_version(group, bundle)
602
+ Dir["#{oh_runtime}/system/#{group.tr(".", "/")}/#{bundle}/*"].map { |version| version.split("/").last }.max
603
+ end
604
+
605
+ def find_jar_version(path, bundle)
606
+ prefix = "#{path}/#{bundle}-"
607
+ Dir["#{prefix}*.jar"].map { |jar| jar.split("-", 2).last[0...-4] }.max
608
+ end
609
+
610
+ def load_boot_jars
611
+ (Dir["#{oh_runtime}/lib/boot/*.jar"] +
612
+ Dir["#{oh_runtime}/lib/endorsed/*.jar"] +
613
+ Dir["#{oh_runtime}/lib/jdk9plus/*.jar"]).each do |jar|
614
+ require jar
615
+ end
616
+ end
617
+
618
+ def set_env
619
+ ENV["DIRNAME"] = "#{oh_runtime}/bin"
620
+ ENV["KARAF_HOME"] = oh_runtime
621
+ if private_confdir
622
+ ENV["OPENHAB_CONF"] = "#{path}/conf"
623
+ FileUtils.mkdir_p([
624
+ "#{path}/conf/items",
625
+ "#{path}/conf/things",
626
+ "#{path}/conf/scripts",
627
+ "#{path}/conf/rules",
628
+ "#{path}/conf/persistence",
629
+ "#{path}/conf/sitemaps",
630
+ "#{path}/conf/transform"
631
+ ])
632
+ end
633
+ Shell.source_env_from("#{oh_runtime}/bin/setenv")
634
+ end
635
+
636
+ def set_java_properties
637
+ [ENV.fetch("JAVA_OPTS", nil), ENV.fetch("EXTRA_JAVA_OPTS", nil)].compact.each do |java_opts|
638
+ Shellwords.split(java_opts).each do |arg|
639
+ next unless arg.start_with?("-D")
640
+
641
+ k, v = arg[2..].split("=", 2)
642
+ java.lang.System.set_property(k, v)
643
+ end
644
+ end
645
+ end
646
+
647
+ # we can't set Java ENV directly, so we have to try and set some things
648
+ # as system properties
649
+ def set_java_properties_from_env
650
+ ENV.each do |(k, v)|
651
+ next unless k.match?(/^(?:KARAF|OPENHAB)_/)
652
+
653
+ prop = k.downcase.tr("_", ".")
654
+ next unless java.lang.System.get_property(prop).nil?
655
+
656
+ java.lang.System.set_property(prop, v)
657
+ end
658
+ end
659
+
660
+ def oh_home
661
+ @oh_home ||= ENV.fetch("OPENHAB_HOME", "/usr/share/openhab")
662
+ end
663
+
664
+ def oh_runtime
665
+ @oh_runtime ||= ENV.fetch("OPENHAB_RUNTIME", "#{oh_home}/runtime")
666
+ end
667
+
668
+ def oh_conf
669
+ @oh_conf ||= ENV.fetch("OPENHAB_CONF")
670
+ end
671
+
672
+ def oh_userdata
673
+ @oh_userdata ||= java.lang.System.get_property("openhab.userdata")
674
+ end
675
+
676
+ def felix_cm
677
+ @felix_cm ||= use_root_instance ? ENV.fetch("OPENHAB_USERDATA") : "#{path}/config"
678
+ end
679
+
680
+ def cleanup_instance
681
+ cleanup_clone
682
+ minimize_installed_features
683
+ filter_addons
684
+ end
685
+
686
+ def cleanup_clone
687
+ FileUtils.rm_rf(["#{oh_userdata}/cache",
688
+ "#{oh_userdata}/jsondb/backup",
689
+ "#{oh_userdata}/marketplace",
690
+ "#{oh_userdata}/logs/*",
691
+ "#{oh_userdata}/tmp/*",
692
+ "#{oh_userdata}/jsondb/org.openhab.marketplace.json",
693
+ "#{oh_userdata}/jsondb/org.openhab.jsonaddonservice.json",
694
+ "#{path}/config/org/apache/felix/fileinstall",
695
+ "#{felix_cm}/org/openhab/jsonaddonservice.config"])
696
+ FileUtils.rm_rf("#{oh_userdata}/jsondb") unless include_jsondb
697
+ end
698
+
699
+ def filter_addons
700
+ config_file = "#{path}/etc/org.apache.felix.fileinstall-deploy.cfg"
701
+ return unless File.exist?(config_file)
702
+
703
+ config = File.read(config_file)
704
+ new_config = config.sub(/^(felix\.fileinstall\.filter\s+=)[^\n]+$/, "\\1 .*/openhab-addons-[^/]+\\.kar")
705
+
706
+ return if config == new_config
707
+
708
+ File.write(config_file, new_config)
709
+ end
710
+
711
+ def prune_startlevels
712
+ config_file = java.lang.System.get_property("openhab.servicecfg")
713
+ return unless File.exist?(config_file)
714
+
715
+ startlevels = File.read(config_file)
716
+ startlevels.sub!(",rules:refresh,rules:dslprovider", "")
717
+
718
+ target_file = "#{oh_userdata}/services.cfg"
719
+ target_file_contents = File.read(target_file) if File.exist?(target_file)
720
+ File.write(target_file, startlevels) unless target_file_contents == startlevels
721
+ java.lang.System.set_property("openhab.servicecfg", target_file)
722
+ end
723
+
724
+ # workaround for https://github.com/openhab/openhab-core/pull/3092
725
+ def reset_start_level_service
726
+ sls = OSGi.service("org.openhab.core.service.StartLevelService")
727
+
728
+ unless sls
729
+ # try a different (hacky!) way to get it, since in OpenHAB 3.2.0 it's not exposed as a service
730
+ scr = OSGi.service("org.osgi.service.component.runtime.ServiceComponentRuntime")
731
+ scr.class.field_reader :componentRegistry
732
+ cr = scr.componentRegistry
733
+
734
+ oh_core_bundle = org.osgi.framework.FrameworkUtil.get_bundle(org.openhab.core.OpenHAB)
735
+ ch = cr.get_component_holder(oh_core_bundle, "org.openhab.core.service.StartLevelService")
736
+ sls = ch&.components&.first&.component_instance&.instance
737
+ end
738
+
739
+ # no SLS yet? then we couldn't have hit the bug
740
+ return unless sls
741
+
742
+ rs = OSGi.service("org.openhab.core.service.ReadyService")
743
+ sls.class.field_reader :trackers, :markers
744
+ rs.class.field_reader :trackers
745
+ return unless sls.markers.empty?
746
+ # SLS thinks it has trackers that RS doesn't?! Yeah, we hit the bug
747
+ return if (sls.trackers.values - rs.trackers.keys).empty?
748
+
749
+ ca = OSGi.service("org.osgi.service.cm.ConfigurationAdmin")
750
+ cfg = ca.get_configuration("org.openhab.startlevel", nil)
751
+ props = cfg.properties
752
+ config = props.keys.to_h { |k| [k, props.get(k)] }
753
+ m = sls.class.java_class.get_declared_method("modified", java.util.Map)
754
+ m.accessible = true
755
+ sls.trackers.clear
756
+ m.invoke(sls, config)
757
+ end
758
+
759
+ def minimize_installed_features
760
+ # cuts down openhab-runtime-base significantly, makes sure
761
+ # openhab-runtime-ui doesn't get installed (from profile.cfg),
762
+ # double-makes-sure no addons get installed, and marks several
763
+ # bundles to not actually start, even though they must still be
764
+ # installed to meet dependencies
765
+ version = find_maven_jar_version("org.openhab.core.bundles", "org.openhab.core")
766
+ File.write("#{oh_userdata}/etc/org.apache.karaf.features.xml", <<~XML)
767
+ <?xml version="1.0" encoding="UTF-8"?>
768
+ <featuresProcessing xmlns="http://karaf.apache.org/xmlns/features-processing/v1.0.0" xmlns:f="http://karaf.apache.org/xmlns/features/v1.6.0">
769
+ <!-- From OpenHAB 3.2.0 -->
770
+ <bundleReplacements>
771
+ <bundle originalUri="mvn:org.ops4j.pax.logging/pax-logging-api/[0,2.0.13)" replacement="mvn:org.ops4j.pax.logging/pax-logging-api/2.0.13" mode="maven" />
772
+ <bundle originalUri="mvn:org.ops4j.pax.logging/pax-logging-log4j2/[0,2.0.13)" replacement="mvn:org.ops4j.pax.logging/pax-logging-log4j2/2.0.13" mode="maven" />
773
+ <bundle originalUri="mvn:org.ops4j.pax.logging/pax-logging-logback/[0,2.0.13)" replacement="mvn:org.ops4j.pax.logging/pax-logging-logback/2.0.13" mode="maven" />
774
+ </bundleReplacements>
775
+
776
+ <blacklistedFeatures>
777
+ <feature>openhab-runtime-ui</feature>
778
+ <feature>openhab-core-ui*</feature>
779
+ <feature>openhab-misc-*</feature>
780
+ <feature>openhab-persistence-*</feature>
781
+ <feature>openhab-package-standard</feature>
782
+ <feature>openhab-ui-*</feature>
783
+ <feature>openhab-voice-*</feature>
784
+ </blacklistedFeatures>
785
+ <featureReplacements>
786
+ <replacement mode="replace">
787
+ <feature name="openhab-runtime-base" version="#{version.sub("-", ".")}">
788
+ <f:feature>openhab-core-base</f:feature>
789
+ <f:feature>openhab-core-automation-module-script</f:feature>
790
+ <f:feature>openhab-core-automation-module-script-rulesupport</f:feature>
791
+ <f:feature>openhab-core-automation-module-media</f:feature>
792
+ <f:feature>openhab-core-model-item</f:feature>
793
+ <f:feature>openhab-core-model-persistence</f:feature>
794
+ <f:feature>openhab-core-model-rule</f:feature>
795
+ <f:feature>openhab-core-model-script</f:feature>
796
+ <f:feature>openhab-core-model-sitemap</f:feature>
797
+ <f:feature>openhab-core-model-thing</f:feature>
798
+ <f:feature>openhab-core-storage-json</f:feature>
799
+ <f:feature>openhab-automation-jrubyscripting</f:feature>
800
+ <f:feature>openhab-transport-http</f:feature>
801
+ <f:feature prerequisite="true">wrapper</f:feature>
802
+ <f:bundle>mvn:org.openhab.core.bundles/org.openhab.core.karaf/#{version}</f:bundle>
803
+ </feature>
804
+ </replacement>
805
+ </featureReplacements>
806
+ </featuresProcessing>
807
+ XML
808
+ end
809
+ end
810
+ end
811
+ end