openhab-jrubyscripting 5.0.0.rc1

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.
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