Refactor ThingHandlerService to an OSGi component prototype (#3957)

Also-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
Signed-off-by: J-N-K <github@klug.nrw>
This commit is contained in:
J-N-K 2024-01-02 13:09:51 +01:00 committed by GitHub
parent 1ddbe3180a
commit a5316f920e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 330 additions and 99 deletions

View File

@ -61,6 +61,12 @@
<version>1.5.0</version> <version>1.5.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
<version>1.5.0</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.apache.felix</groupId> <groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr</artifactId> <artifactId>org.apache.felix.scr</artifactId>

View File

@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.i18n.I18nUtil; import org.openhab.core.i18n.I18nUtil;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
@ -347,10 +348,9 @@ public abstract class AbstractDiscoveryService implements DiscoveryService {
*/ */
protected void activate(@Nullable Map<String, Object> configProperties) { protected void activate(@Nullable Map<String, Object> configProperties) {
if (configProperties != null) { if (configProperties != null) {
Object property = configProperties.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); backgroundDiscoveryEnabled = ConfigParser.valueAsOrElse(
if (property != null) { configProperties.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class,
backgroundDiscoveryEnabled = getAutoDiscoveryEnabled(property); backgroundDiscoveryEnabled);
}
} }
if (backgroundDiscoveryEnabled) { if (backgroundDiscoveryEnabled) {
startBackgroundDiscovery(); startBackgroundDiscovery();
@ -370,20 +370,18 @@ public abstract class AbstractDiscoveryService implements DiscoveryService {
*/ */
protected void modified(@Nullable Map<String, Object> configProperties) { protected void modified(@Nullable Map<String, Object> configProperties) {
if (configProperties != null) { if (configProperties != null) {
Object property = configProperties.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY); boolean enabled = ConfigParser.valueAsOrElse(
if (property != null) { configProperties.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class,
boolean enabled = getAutoDiscoveryEnabled(property); backgroundDiscoveryEnabled);
if (backgroundDiscoveryEnabled && !enabled) { if (backgroundDiscoveryEnabled && !enabled) {
stopBackgroundDiscovery(); stopBackgroundDiscovery();
logger.debug("Background discovery for discovery service '{}' disabled.", logger.debug("Background discovery for discovery service '{}' disabled.", this.getClass().getName());
this.getClass().getName()); } else if (!backgroundDiscoveryEnabled && enabled) {
} else if (!backgroundDiscoveryEnabled && enabled) { startBackgroundDiscovery();
startBackgroundDiscovery(); logger.debug("Background discovery for discovery service '{}' enabled.", this.getClass().getName());
logger.debug("Background discovery for discovery service '{}' enabled.", this.getClass().getName());
}
backgroundDiscoveryEnabled = enabled;
} }
backgroundDiscoveryEnabled = enabled;
} }
} }
@ -426,14 +424,6 @@ public abstract class AbstractDiscoveryService implements DiscoveryService {
return timestampOfLastScan; return timestampOfLastScan;
} }
private boolean getAutoDiscoveryEnabled(Object autoDiscoveryEnabled) {
if (autoDiscoveryEnabled instanceof String string) {
return Boolean.parseBoolean(string);
} else {
return Boolean.TRUE.equals(autoDiscoveryEnabled);
}
}
private String inferKey(DiscoveryResult discoveryResult, String lastSegment) { private String inferKey(DiscoveryResult discoveryResult, String lastSegment) {
return "discovery." + discoveryResult.getThingUID().getAsString().replace(":", ".") + "." + lastSegment; return "discovery." + discoveryResult.getThingUID().getAsString().replace(":", ".") + "." + lastSegment;
} }

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.config.discovery;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractThingHandlerDiscoveryService} extends the {@link AbstractDiscoveryService} for thing-based
* discovery services.
*
* It handles the injection of the {@link ThingHandler}
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractThingHandlerDiscoveryService<T extends ThingHandler> extends AbstractDiscoveryService
implements ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(AbstractThingHandlerDiscoveryService.class);
private final Class<T> thingClazz;
private boolean backgroundDiscoveryEnabled = false;
// this works around a bug in ecj: @NonNullByDefault({}) complains about the field not being
// initialized when the type is generic, so we have to initialize it with "something"
protected @NonNullByDefault({}) T thingHandler = (@NonNull T) null;
protected AbstractThingHandlerDiscoveryService(Class<T> thingClazz, @Nullable Set<ThingTypeUID> supportedThingTypes,
int timeout, boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException {
super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault);
this.thingClazz = thingClazz;
}
protected AbstractThingHandlerDiscoveryService(Class<T> thingClazz, @Nullable Set<ThingTypeUID> supportedThingTypes,
int timeout) throws IllegalArgumentException {
super(supportedThingTypes, timeout);
this.thingClazz = thingClazz;
}
protected AbstractThingHandlerDiscoveryService(Class<T> thingClazz, int timeout) throws IllegalArgumentException {
super(timeout);
this.thingClazz = thingClazz;
}
@Override
protected abstract void startScan();
@Override
@SuppressWarnings("unchecked")
public void setThingHandler(ThingHandler handler) {
if (thingClazz.isAssignableFrom(handler.getClass())) {
this.thingHandler = (T) handler;
} else {
throw new IllegalArgumentException(
"Expected class is " + thingClazz + " but the parameter has class " + handler.getClass());
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return thingHandler;
}
@Override
public void activate(@Nullable Map<String, Object> config) {
// do not call super.activate here, otherwise the scan might be background scan might be started before the
// thing handler is set. This is correctly handled in initialize
if (config != null) {
backgroundDiscoveryEnabled = ConfigParser.valueAsOrElse(
config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class, false);
}
}
@Override
public void modified(@Nullable Map<String, Object> config) {
if (config != null) {
boolean enabled = ConfigParser.valueAsOrElse(
config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class, false);
if (backgroundDiscoveryEnabled && !enabled) {
stopBackgroundDiscovery();
logger.debug("Background discovery for discovery service '{}' disabled.", getClass().getName());
} else if (!backgroundDiscoveryEnabled && enabled) {
startBackgroundDiscovery();
logger.debug("Background discovery for discovery service '{}' enabled.", getClass().getName());
}
backgroundDiscoveryEnabled = enabled;
}
}
@Override
public void deactivate() {
// do not call super.deactivate here, background scan is already handled in dispose
}
@Override
public void initialize() {
if (backgroundDiscoveryEnabled) {
startBackgroundDiscovery();
logger.debug("Background discovery for discovery service '{}' enabled.", getClass().getName());
}
}
@Override
public void dispose() {
if (backgroundDiscoveryEnabled) {
stopBackgroundDiscovery();
}
}
}

View File

@ -15,9 +15,9 @@ package org.openhab.core.thing.binding;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -34,8 +34,12 @@ import org.openhab.core.thing.binding.firmware.FirmwareUpdateHandler;
import org.openhab.core.thing.type.ThingType; import org.openhab.core.thing.type.ThingType;
import org.openhab.core.thing.type.ThingTypeRegistry; import org.openhab.core.thing.type.ThingTypeRegistry;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration; import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext; import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -52,10 +56,13 @@ import org.slf4j.LoggerFactory;
* @author Thomas Höfer - added config status provider and firmware update handler service registration * @author Thomas Höfer - added config status provider and firmware update handler service registration
* @author Stefan Bußweiler - API changes due to bridge/thing life cycle refactoring, removed OSGi service registration * @author Stefan Bußweiler - API changes due to bridge/thing life cycle refactoring, removed OSGi service registration
* for thing handlers * for thing handlers
* @author Connor Petty - added osgi service registration for thing handler services.
*/ */
@NonNullByDefault @NonNullByDefault
public abstract class BaseThingHandlerFactory implements ThingHandlerFactory { public abstract class BaseThingHandlerFactory implements ThingHandlerFactory {
private static final String THING_HANDLER_SERVICE_CANONICAL_NAME = ThingHandlerService.class.getCanonicalName();
protected @NonNullByDefault({}) BundleContext bundleContext; protected @NonNullByDefault({}) BundleContext bundleContext;
private final Logger logger = LoggerFactory.getLogger(BaseThingHandlerFactory.class); private final Logger logger = LoggerFactory.getLogger(BaseThingHandlerFactory.class);
@ -63,7 +70,7 @@ public abstract class BaseThingHandlerFactory implements ThingHandlerFactory {
private final Map<String, ServiceRegistration<ConfigStatusProvider>> configStatusProviders = new ConcurrentHashMap<>(); private final Map<String, ServiceRegistration<ConfigStatusProvider>> configStatusProviders = new ConcurrentHashMap<>();
private final Map<String, ServiceRegistration<FirmwareUpdateHandler>> firmwareUpdateHandlers = new ConcurrentHashMap<>(); private final Map<String, ServiceRegistration<FirmwareUpdateHandler>> firmwareUpdateHandlers = new ConcurrentHashMap<>();
private final Map<ThingUID, Set<ServiceRegistration<?>>> thingHandlerServices = new ConcurrentHashMap<>(); private final Map<ThingUID, Set<RegisteredThingHandlerService<?>>> thingHandlerServices = new ConcurrentHashMap<>();
private @NonNullByDefault({}) ServiceTracker<ThingTypeRegistry, ThingTypeRegistry> thingTypeRegistryServiceTracker; private @NonNullByDefault({}) ServiceTracker<ThingTypeRegistry, ThingTypeRegistry> thingTypeRegistryServiceTracker;
private @NonNullByDefault({}) ServiceTracker<ConfigDescriptionRegistry, ConfigDescriptionRegistry> configDescriptionRegistryServiceTracker; private @NonNullByDefault({}) ServiceTracker<ConfigDescriptionRegistry, ConfigDescriptionRegistry> configDescriptionRegistryServiceTracker;
@ -143,67 +150,72 @@ public abstract class BaseThingHandlerFactory implements ThingHandlerFactory {
private void registerServices(Thing thing, ThingHandler thingHandler) { private void registerServices(Thing thing, ThingHandler thingHandler) {
ThingUID thingUID = thing.getUID(); ThingUID thingUID = thing.getUID();
for (Class<?> c : thingHandler.getServices()) { for (Class<? extends ThingHandlerService> c : thingHandler.getServices()) {
if (!ThingHandlerService.class.isAssignableFrom(c)) {
logger.warn(
"Should register service={} for thingUID={}, but it does not implement the interface ThingHandlerService.",
c.getCanonicalName(), thingUID);
continue;
}
registerThingHandlerService(thingUID, thingHandler, c);
}
}
private <T extends ThingHandlerService> void registerThingHandlerService(ThingUID thingUID,
ThingHandler thingHandler, Class<T> c) {
RegisteredThingHandlerService<T> registeredService;
Component component = c.getAnnotation(Component.class);
if (component != null && component.enabled()) {
if (component.scope() != ServiceScope.PROTOTYPE) {
// then we cannot use it.
logger.warn("Could not register service for class={}. Service must have a prototype scope",
c.getCanonicalName());
return;
}
if (component.service().length != 1 || component.service()[0] != c) {
logger.warn(
"Could not register service for class={}. ThingHandlerService with @Component must only label itself as a service.",
c.getCanonicalName());
return;
}
}
ServiceReference<T> serviceRef = bundleContext.getServiceReference(c);
if (serviceRef != null) {
ServiceObjects<T> serviceObjs = bundleContext.getServiceObjects(serviceRef);
registeredService = new RegisteredThingHandlerService<>(serviceObjs);
} else {
try { try {
Object serviceInstance = c.getConstructor().newInstance(); T serviceInstance = c.getConstructor().newInstance();
registeredService = new RegisteredThingHandlerService<>(serviceInstance);
ThingHandlerService ths = null;
if (serviceInstance instanceof ThingHandlerService service) {
ths = service;
ths.setThingHandler(thingHandler);
} else {
logger.warn(
"Should register service={} for thingUID={}, but it does not implement the interface ThingHandlerService.",
c.getCanonicalName(), thingUID);
continue;
}
Set<Class<?>> interfaces = getAllInterfaces(c);
List<String> serviceNames = new LinkedList<>();
interfaces.forEach(i -> {
String className = i.getCanonicalName();
// we only add specific ThingHandlerServices, i.e. those that derive from the ThingHandlerService
// interface, NOT the ThingHandlerService itself. We do this to register them as specific OSGi
// services later, rather than as a generic ThingHandlerService.
if (className != null && !className.equals(ThingHandlerService.class.getCanonicalName())) {
serviceNames.add(className);
}
});
if (!serviceNames.isEmpty()) {
String[] serviceNamesArray = serviceNames.toArray(new String[serviceNames.size()]);
ServiceRegistration<?> serviceReg = bundleContext.registerService(serviceNamesArray,
serviceInstance, null);
if (serviceReg != null) {
Set<ServiceRegistration<?>> serviceRegs = thingHandlerServices.get(thingUID);
if (serviceRegs == null) {
Set<ServiceRegistration<?>> set = new HashSet<>();
set.add(serviceReg);
thingHandlerServices.put(thingUID, set);
} else {
serviceRegs.add(serviceReg);
}
ths.activate();
}
}
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
| InvocationTargetException e) { | InvocationTargetException e) {
logger.warn("Could not register service for class={}", c.getCanonicalName(), e); logger.warn("Could not register service for class={}", c.getCanonicalName(), e);
return;
} }
} }
String[] serviceNames = getAllInterfaces(c).stream()//
.map(Class::getCanonicalName)
// we only add specific ThingHandlerServices, i.e. those that derive from the
// ThingHandlerService
// interface, NOT the ThingHandlerService itself. We do this to register them as specific OSGi
// services later, rather than as a generic ThingHandlerService.
.filter(className -> className != null && !className.equals(THING_HANDLER_SERVICE_CANONICAL_NAME))
.toArray(String[]::new);
registeredService.initializeService(thingHandler, serviceNames);
Objects.requireNonNull(thingHandlerServices.computeIfAbsent(thingUID, uid -> new HashSet<>()))
.add(registeredService);
} }
private void unregisterServices(Thing thing) { private void unregisterServices(Thing thing) {
ThingUID thingUID = thing.getUID(); ThingUID thingUID = thing.getUID();
Set<ServiceRegistration<?>> serviceRegs = thingHandlerServices.remove(thingUID); Set<RegisteredThingHandlerService<?>> serviceRegs = thingHandlerServices.remove(thingUID);
if (serviceRegs != null) { if (serviceRegs != null) {
serviceRegs.forEach(serviceReg -> { serviceRegs.forEach(RegisteredThingHandlerService::disposeService);
ThingHandlerService ths = (ThingHandlerService) getBundleContext()
.getService(serviceReg.getReference());
serviceReg.unregister();
if (ths != null) {
ths.deactivate();
}
});
} }
} }
@ -213,7 +225,7 @@ public abstract class BaseThingHandlerFactory implements ThingHandlerFactory {
* @param clazz The class * @param clazz The class
* @return A {@link List} of interfaces * @return A {@link List} of interfaces
*/ */
private Set<Class<?>> getAllInterfaces(Class<?> clazz) { private static Set<Class<?>> getAllInterfaces(Class<?> clazz) {
Set<Class<?>> interfaces = new HashSet<>(); Set<Class<?>> interfaces = new HashSet<>();
for (Class<?> superclazz = clazz; superclazz != null; superclazz = superclazz.getSuperclass()) { for (Class<?> superclazz = clazz; superclazz != null; superclazz = superclazz.getSuperclass()) {
interfaces.addAll(Arrays.asList(superclazz.getInterfaces())); interfaces.addAll(Arrays.asList(superclazz.getInterfaces()));
@ -354,4 +366,47 @@ public abstract class BaseThingHandlerFactory implements ThingHandlerFactory {
} }
return configDescriptionRegistryServiceTracker.getService(); return configDescriptionRegistryServiceTracker.getService();
} }
private class RegisteredThingHandlerService<T extends ThingHandlerService> {
private final T serviceInstance;
private @Nullable ServiceObjects<T> serviceObjects;
private @Nullable ServiceRegistration<?> serviceRegistration;
public RegisteredThingHandlerService(T serviceInstance) {
this.serviceInstance = serviceInstance;
}
public RegisteredThingHandlerService(ServiceObjects<T> serviceObjs) {
this.serviceInstance = serviceObjs.getService();
this.serviceObjects = serviceObjs;
}
public void initializeService(ThingHandler handler, String[] serviceNames) {
serviceInstance.setThingHandler(handler);
if (serviceNames.length > 0) {
ServiceRegistration<?> serviceReg = bundleContext.registerService(serviceNames, serviceInstance, null);
if (serviceReg != null) {
serviceRegistration = serviceReg;
}
}
serviceInstance.initialize();
}
public void disposeService() {
serviceInstance.dispose();
ServiceRegistration<?> serviceReg = this.serviceRegistration;
if (serviceReg != null) {
serviceReg.unregister();
}
ServiceObjects<T> serviceObjs = this.serviceObjects;
if (serviceObjs != null) {
serviceObjs.ungetService(serviceInstance);
}
}
}
} }

View File

@ -40,14 +40,44 @@ public interface ThingHandlerService {
ThingHandler getThingHandler(); ThingHandler getThingHandler();
/** /**
* Method that will be called if this service will be activated * This method is used by the framework during activation of the OSGi component.
* It is called BEFORE the thing handler is set.
*
* See {@link #initialize()}, {@link #deactivate()}
*/ */
default void activate() { default void activate() {
} }
/** /**
* Method that will be called if this service will be deactivated * This method is used by the framework during de-activation of the OSGi component.
* It is NOT guaranteed that the thing handler is still valid.
*
* See {@link #dispose()}, {@link #activate()}
*/ */
default void deactivate() { default void deactivate() {
} }
/**
* This method is used by the framework during activation of the service.
* It is called AFTER the component is fully activated and thing handler has been set.
*
* Implementations should override this method to add additional initialization code. This method should call
* <code>super.initialize()</code> to ensure background discovery is properly handled.
*
* See {@link #activate(), #{@link #dispose()}
*/
default void initialize() {
}
/**
* This method is used by the framework during de-activation of the service.
* It is called while the component is still activated.
*
* Code depending on an activated service should go here. This method should call <code>super.dispose()</code> to
* ensure background discovery is properly handled.
*
* See {@link #deactivate()}, {@link #initialize()}
*/
default void dispose() {
}
} }

View File

@ -34,6 +34,7 @@
<bundle dependency="true">mvn:tech.units/indriya/2.2</bundle> <bundle dependency="true">mvn:tech.units/indriya/2.2</bundle>
<bundle dependency="true">mvn:tech.uom.lib/uom-lib-common/2.2</bundle> <bundle dependency="true">mvn:tech.uom.lib/uom-lib-common/2.2</bundle>
<bundle dependency="true">mvn:org.apiguardian/apiguardian-api/1.1.2</bundle> <bundle dependency="true">mvn:org.apiguardian/apiguardian-api/1.1.2</bundle>
<bundle dependency="true">mvn:org.osgi/org.osgi.service.component.annotations/1.5.0</bundle>
<!-- TODO: Unbundled libraries --> <!-- TODO: Unbundled libraries -->
<bundle dependency="true">mvn:com.thoughtworks.xstream/xstream/1.4.20</bundle> <bundle dependency="true">mvn:com.thoughtworks.xstream/xstream/1.4.20</bundle>

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.automation
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.automation
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -71,4 +71,5 @@ Fragment-Host: org.openhab.core.automation.module.script
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.automation
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.automation
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.config.discovery.mdns
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -73,4 +73,5 @@ Fragment-Host: org.openhab.core.config.discovery
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.config.discovery.usbserial.linuxsysfs
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -82,4 +82,5 @@ Provide-Capability: \
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -105,4 +105,6 @@ Fragment-Host: org.openhab.core.io.rest.core
org.openhab.core.semantics;version='[4.2.0,4.2.1)',\ org.openhab.core.semantics;version='[4.2.0,4.2.1)',\
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)' org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -110,7 +110,6 @@ Fragment-Host: org.openhab.core.model.item
org.openhab.core.model.item.tests;version='[4.2.0,4.2.1)',\ org.openhab.core.model.item.tests;version='[4.2.0,4.2.1)',\
org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\ org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\ org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.sitemap;version='[4.2.0,4.2.1)',\ org.openhab.core.model.sitemap;version='[4.2.0,4.2.1)',\
@ -120,4 +119,6 @@ Fragment-Host: org.openhab.core.model.item
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.openhab.core.voice;version='[4.2.0,4.2.1)' org.openhab.core.voice;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -123,4 +123,7 @@ Fragment-Host: org.openhab.core.model.rule.runtime
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.openhab.core.voice;version='[4.2.0,4.2.1)' org.openhab.core.voice;version='[4.2.0,4.2.1)',\
org.openhab.core.model.item.runtime;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -116,7 +116,6 @@ Fragment-Host: org.openhab.core.model.script
org.openhab.core.model.item;version='[4.2.0,4.2.1)',\ org.openhab.core.model.item;version='[4.2.0,4.2.1)',\
org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\ org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\ org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script.tests;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script.tests;version='[4.2.0,4.2.1)',\
@ -127,4 +126,6 @@ Fragment-Host: org.openhab.core.model.script
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.openhab.core.voice;version='[4.2.0,4.2.1)' org.openhab.core.voice;version='[4.2.0,4.2.1)',\
org.openhab.core.model.item.runtime;version='[4.2.0,4.2.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -116,7 +116,6 @@ Fragment-Host: org.openhab.core.model.thing
org.openhab.core.model.item.runtime;version='[4.2.0,4.2.1)',\ org.openhab.core.model.item.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\ org.openhab.core.model.persistence;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\ org.openhab.core.model.rule;version='[4.2.0,4.2.1)',\
org.openhab.core.model.rule.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script;version='[4.2.0,4.2.1)',\
org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\ org.openhab.core.model.script.runtime;version='[4.2.0,4.2.1)',\
org.openhab.core.model.sitemap;version='[4.2.0,4.2.1)',\ org.openhab.core.model.sitemap;version='[4.2.0,4.2.1)',\
@ -129,4 +128,6 @@ Fragment-Host: org.openhab.core.model.thing
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.openhab.core.voice;version='[4.2.0,4.2.1)' org.openhab.core.voice;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -67,4 +67,5 @@ Fragment-Host: org.openhab.core.storage.json
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)' org.osgi.service.cm;version='[1.6.0,1.6.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -74,4 +74,5 @@ Fragment-Host: org.openhab.core.thing
org.openhab.core.test;version='[4.2.0,4.2.1)',\ org.openhab.core.test;version='[4.2.0,4.2.1)',\
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.thing.tests;version='[4.2.0,4.2.1)',\ org.openhab.core.thing.tests;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)' org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'

View File

@ -79,4 +79,5 @@ Fragment-Host: org.openhab.core.voice
org.openhab.core.thing;version='[4.2.0,4.2.1)',\ org.openhab.core.thing;version='[4.2.0,4.2.1)',\
org.openhab.core.transform;version='[4.2.0,4.2.1)',\ org.openhab.core.transform;version='[4.2.0,4.2.1)',\
org.openhab.core.voice;version='[4.2.0,4.2.1)',\ org.openhab.core.voice;version='[4.2.0,4.2.1)',\
org.openhab.core.voice.tests;version='[4.2.0,4.2.1)' org.openhab.core.voice.tests;version='[4.2.0,4.2.1)',\
org.osgi.service.component.annotations;version='[1.5.0,1.5.1)'