mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 21:31:53 +01:00
Refactor ThingManagerImpl (#3330)
* Refactor ThingManagerImpl This - moves config description URI to type-base class - decouples the thing manager from bundle loading - makes sure that all thing/channel-types and config descriptions are available when the thing is initialized - enables thing type updates Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
7f5694856d
commit
d7fa5534bf
@ -206,18 +206,14 @@ public class ConfigUtil {
|
||||
* @param configuration the configuration to be normalized
|
||||
* @param configDescriptions the configuration descriptions that should be applied (must not be empty).
|
||||
* @return the normalized configuration or null if given configuration was null
|
||||
* @throws IllegalArgumentExcetpion if given config description is null
|
||||
* @throws IllegalArgumentException if given config description is null
|
||||
*/
|
||||
public static @Nullable Map<String, Object> normalizeTypes(@Nullable Map<String, Object> configuration,
|
||||
public static Map<String, Object> normalizeTypes(Map<String, Object> configuration,
|
||||
List<ConfigDescription> configDescriptions) {
|
||||
if (configDescriptions.isEmpty()) {
|
||||
throw new IllegalArgumentException("Config description must not be empty.");
|
||||
}
|
||||
|
||||
if (configuration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> convertedConfiguration = new HashMap<>();
|
||||
|
||||
Map<String, ConfigDescriptionParameter> configParams = new HashMap<>();
|
||||
|
@ -11,3 +11,8 @@ min_value_numeric_violated=The value must not be less than {0}.
|
||||
pattern_violated=The value {0} does not match the pattern {1}.
|
||||
options_violated=The value {0} does not match allowed parameter options. Allowed options are: {1}
|
||||
multiple_limit_violated=Only {0} elements are allowed but {1} are provided.
|
||||
|
||||
bridge_not_configured=Configuring a bridge is mandatory.
|
||||
type_description_missing=Type description for '{0}' not found also we checked the presence before.
|
||||
config_description_missing=Config description for '{0}' not found also we checked the presence before.
|
||||
|
||||
|
@ -25,6 +25,11 @@
|
||||
<artifactId>org.openhab.core.io.console</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>2.3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.test</artifactId>
|
||||
@ -33,4 +38,48 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jvnet.jaxb2.maven2</groupId>
|
||||
<artifactId>maven-jaxb2-plugin</artifactId>
|
||||
<version>0.15.2</version>
|
||||
<configuration>
|
||||
<schemaDirectory>src/main/resources/xsd</schemaDirectory>
|
||||
<noFileHeader>true</noFileHeader>
|
||||
<locale>en</locale>
|
||||
<episode>false</episode>
|
||||
<extension>true</extension>
|
||||
<args>
|
||||
<arg>-Xxew</arg>
|
||||
<arg>-Xxew:instantiate early</arg>
|
||||
</args>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.jaxb-xew-plugin</groupId>
|
||||
<artifactId>jaxb-xew-plugin</artifactId>
|
||||
<version>1.10</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- Required for JDK 17 compatibility, see: https://github.com/highsource/maven-jaxb2-plugin/issues/207 -->
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>2.3.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-jaxb-sources</id>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
@ -23,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@NonNullByDefault
|
||||
public enum ThingStatusDetail {
|
||||
NONE,
|
||||
NOT_YET_READY,
|
||||
HANDLER_MISSING_ERROR,
|
||||
HANDLER_REGISTERING_ERROR,
|
||||
HANDLER_INITIALIZING_ERROR,
|
||||
|
@ -14,9 +14,11 @@ package org.openhab.core.thing.binding.builder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -74,6 +76,19 @@ public class ThingBuilder {
|
||||
return new ThingBuilder(thingTypeUID, thingUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new thing {@link ThingBuilder} for a copy of the given thing
|
||||
*
|
||||
* @param thing the {@link Thing} to create this builder from
|
||||
* @return the created {@link ThingBuilder}
|
||||
*
|
||||
*/
|
||||
public static ThingBuilder create(Thing thing) {
|
||||
return ThingBuilder.create(thing.getThingTypeUID(), thing.getUID()).withBridge(thing.getBridgeUID())
|
||||
.withChannels(thing.getChannels()).withConfiguration(thing.getConfiguration())
|
||||
.withLabel(thing.getLabel()).withLocation(thing.getLocation()).withProperties(thing.getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the thing
|
||||
*
|
||||
@ -200,6 +215,20 @@ public class ThingBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set / replace a single property for this thing
|
||||
*
|
||||
* @param key the key / name of the property
|
||||
* @param value the value of the property
|
||||
* @return the {@link ThingBuilder} itself
|
||||
*/
|
||||
public ThingBuilder withProperty(String key, String value) {
|
||||
Map<String, String> oldProperties = Objects.requireNonNullElse(this.properties, Map.of());
|
||||
Map<String, String> newProperties = new HashMap<>(oldProperties);
|
||||
newProperties.put(key, value);
|
||||
return withProperties(newProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/replace the properties for this thing
|
||||
*
|
||||
|
@ -0,0 +1,269 @@
|
||||
/**
|
||||
* 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.thing.internal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelDefinition;
|
||||
import org.openhab.core.thing.type.ChannelGroupType;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.util.ThingHandlerHelper;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ThingHandlerCallbackImpl} implements the {@link ThingHandlerCallback} interface
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ThingHandlerCallbackImpl implements ThingHandlerCallback {
|
||||
private final Logger logger = LoggerFactory.getLogger(ThingHandlerCallbackImpl.class);
|
||||
|
||||
private final ThingManagerImpl thingManager;
|
||||
|
||||
public ThingHandlerCallbackImpl(ThingManagerImpl thingManager) {
|
||||
this.thingManager = thingManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateUpdated(ChannelUID channelUID, State state) {
|
||||
thingManager.communicationManager.stateUpdated(channelUID, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommand(ChannelUID channelUID, Command command) {
|
||||
thingManager.communicationManager.postCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelTriggered(Thing thing, ChannelUID channelUID, String event) {
|
||||
thingManager.communicationManager.channelTriggered(thing, channelUID, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusUpdated(Thing thing, ThingStatusInfo statusInfo) {
|
||||
// note: all operations based on a status update should be executed asynchronously!
|
||||
ThingStatusInfo oldStatusInfo = thing.getStatusInfo();
|
||||
ensureValidStatus(oldStatusInfo.getStatus(), statusInfo.getStatus());
|
||||
|
||||
if (ThingStatus.REMOVING.equals(oldStatusInfo.getStatus())
|
||||
&& !ThingStatus.REMOVED.equals(statusInfo.getStatus())) {
|
||||
// if we go to ONLINE and are still in REMOVING, notify handler about required removal
|
||||
if (ThingStatus.ONLINE.equals(statusInfo.getStatus())) {
|
||||
logger.debug("Handler is initialized now and we try to remove it, because it is in REMOVING state.");
|
||||
thingManager.notifyThingHandlerAboutRemoval(thing);
|
||||
}
|
||||
// only allow REMOVING -> REMOVED transition, all others are ignored because they are illegal
|
||||
logger.debug(
|
||||
"Ignoring illegal status transition for thing {} from REMOVING to {}, only REMOVED would have been allowed.",
|
||||
thing.getUID(), statusInfo.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// update thing status and send event about new status
|
||||
thingManager.setThingStatus(thing, statusInfo);
|
||||
|
||||
// if thing is a bridge
|
||||
if (thing instanceof Bridge bridge) {
|
||||
handleBridgeStatusUpdate(bridge, statusInfo, oldStatusInfo);
|
||||
}
|
||||
// if thing has a bridge
|
||||
if (thing.getBridgeUID() != null) {
|
||||
handleBridgeChildStatusUpdate(thing, oldStatusInfo);
|
||||
}
|
||||
// notify thing registry about thing removal
|
||||
if (ThingStatus.REMOVED.equals(thing.getStatus())) {
|
||||
thingManager.notifyRegistryAboutForceRemove(thing);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureValidStatus(ThingStatus oldStatus, ThingStatus newStatus) {
|
||||
if (!(ThingStatus.UNKNOWN.equals(newStatus) || ThingStatus.ONLINE.equals(newStatus)
|
||||
|| ThingStatus.OFFLINE.equals(newStatus) || ThingStatus.REMOVED.equals(newStatus))) {
|
||||
throw new IllegalArgumentException(
|
||||
MessageFormat.format("Illegal status {0}. Bindings only may set {1}, {2}, {3} or {4}.", newStatus,
|
||||
ThingStatus.UNKNOWN, ThingStatus.ONLINE, ThingStatus.OFFLINE, ThingStatus.REMOVED));
|
||||
}
|
||||
if (ThingStatus.REMOVED.equals(newStatus) && !ThingStatus.REMOVING.equals(oldStatus)) {
|
||||
throw new IllegalArgumentException(
|
||||
MessageFormat.format("Illegal status {0}. The thing was in state {1} and not in {2}", newStatus,
|
||||
oldStatus, ThingStatus.REMOVING));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBridgeStatusUpdate(Bridge bridge, ThingStatusInfo statusInfo, ThingStatusInfo oldStatusInfo) {
|
||||
if (ThingHandlerHelper.isHandlerInitialized(bridge)
|
||||
&& (ThingStatus.INITIALIZING.equals(oldStatusInfo.getStatus()))) {
|
||||
// bridge has just been initialized: initialize child things as well
|
||||
thingManager.registerChildHandlers(bridge);
|
||||
} else if (!statusInfo.equals(oldStatusInfo)) {
|
||||
// bridge status has been changed: notify child things about status change
|
||||
thingManager.notifyThingsAboutBridgeStatusChange(bridge, statusInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBridgeChildStatusUpdate(Thing thing, ThingStatusInfo oldStatusInfo) {
|
||||
if (ThingHandlerHelper.isHandlerInitialized(thing)
|
||||
&& ThingStatus.INITIALIZING.equals(oldStatusInfo.getStatus())) {
|
||||
// child thing has just been initialized: notify bridge about it
|
||||
thingManager.notifyBridgeAboutChildHandlerInitialization(thing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(final Thing thing) {
|
||||
thingManager.thingUpdated(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfigurationParameters(Thing thing, Map<String, Object> configurationParameters) {
|
||||
ThingType thingType = thingManager.thingTypeRegistry.getThingType(thing.getThingTypeUID());
|
||||
if (thingType != null) {
|
||||
URI configDescriptionURI = thingType.getConfigDescriptionURI();
|
||||
if (configDescriptionURI != null) {
|
||||
thingManager.configDescriptionValidator.validate(configurationParameters, configDescriptionURI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfigurationParameters(Channel channel, Map<String, Object> configurationParameters) {
|
||||
ChannelType channelType = thingManager.channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
|
||||
if (channelType != null) {
|
||||
URI configDescriptionURI = channelType.getConfigDescriptionURI();
|
||||
if (configDescriptionURI != null) {
|
||||
thingManager.configDescriptionValidator.validate(configurationParameters, configDescriptionURI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(ChannelTypeUID channelTypeUID) {
|
||||
ChannelType channelType = thingManager.channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
if (channelType != null) {
|
||||
URI configDescriptionUri = channelType.getConfigDescriptionURI();
|
||||
if (configDescriptionUri != null) {
|
||||
return thingManager.configDescriptionRegistry.getConfigDescription(configDescriptionUri);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigDescription getConfigDescription(ThingTypeUID thingTypeUID) {
|
||||
ThingType thingType = thingManager.thingTypeRegistry.getThingType(thingTypeUID);
|
||||
if (thingType != null) {
|
||||
URI configDescriptionUri = thingType.getConfigDescriptionURI();
|
||||
if (configDescriptionUri != null) {
|
||||
return thingManager.configDescriptionRegistry.getConfigDescription(configDescriptionUri);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurationUpdated(Thing thing) {
|
||||
if (!ThingHandlerHelper.isHandlerInitialized(thing)) {
|
||||
thingManager.initializeHandler(thing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateThingType(final Thing thing, final ThingTypeUID thingTypeUID,
|
||||
final Configuration configuration) {
|
||||
thingManager.migrateThingType(thing, thingTypeUID, configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelBuilder createChannelBuilder(ChannelUID channelUID, ChannelTypeUID channelTypeUID) {
|
||||
ChannelType channelType = thingManager.channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
if (channelType == null) {
|
||||
throw new IllegalArgumentException(String.format("Channel type '%s' is not known", channelTypeUID));
|
||||
}
|
||||
return ThingFactoryHelper.createChannelBuilder(channelUID, channelType, thingManager.configDescriptionRegistry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelBuilder editChannel(Thing thing, ChannelUID channelUID) {
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
if (channel == null) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Channel '%s' does not exist for thing '%s'", channelUID, thing.getUID()));
|
||||
}
|
||||
return ChannelBuilder.create(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChannelBuilder> createChannelBuilders(ChannelGroupUID channelGroupUID,
|
||||
ChannelGroupTypeUID channelGroupTypeUID) {
|
||||
ChannelGroupType channelGroupType = thingManager.channelGroupTypeRegistry
|
||||
.getChannelGroupType(channelGroupTypeUID);
|
||||
if (channelGroupType == null) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Channel group type '%s' is not known", channelGroupTypeUID));
|
||||
}
|
||||
List<ChannelBuilder> channelBuilders = new ArrayList<>();
|
||||
for (ChannelDefinition channelDefinition : channelGroupType.getChannelDefinitions()) {
|
||||
ChannelType channelType = thingManager.channelTypeRegistry
|
||||
.getChannelType(channelDefinition.getChannelTypeUID());
|
||||
if (channelType != null) {
|
||||
ChannelUID channelUID = new ChannelUID(channelGroupUID, channelDefinition.getId());
|
||||
ChannelBuilder channelBuilder = ThingFactoryHelper.createChannelBuilder(channelUID, channelDefinition,
|
||||
thingManager.configDescriptionRegistry);
|
||||
if (channelBuilder != null) {
|
||||
channelBuilders.add(channelBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
return channelBuilders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChannelLinked(ChannelUID channelUID) {
|
||||
return thingManager.itemChannelLinkRegistry.isLinked(channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Bridge getBridge(ThingUID bridgeUID) {
|
||||
Thing bridgeThing = thingManager.thingRegistry.get(bridgeUID);
|
||||
if (bridgeThing instanceof Bridge bridge) {
|
||||
return bridge;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.thing.internal.update;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
|
||||
/**
|
||||
* The {@link RemoveChannelInstructionImpl} implements a {@link ThingUpdateInstruction} that removes a channel from a
|
||||
* thing.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoveChannelInstructionImpl implements ThingUpdateInstruction {
|
||||
private final int thingTypeVersion;
|
||||
private final String channelId;
|
||||
|
||||
RemoveChannelInstructionImpl(int thingTypeVersion, String channelId) {
|
||||
this.thingTypeVersion = thingTypeVersion;
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThingTypeVersion() {
|
||||
return thingTypeVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(Thing thing, ThingBuilder thingBuilder) {
|
||||
ChannelUID affectedChannelUid = new ChannelUID(thing.getUID(), channelId);
|
||||
thingBuilder.withoutChannel(affectedChannelUid);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.thing.internal.update;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
|
||||
/**
|
||||
* The {@link ThingUpdateInstruction} is an interface that can be implemented to perform updates on things when the
|
||||
* thing-type changes.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ThingUpdateInstruction {
|
||||
|
||||
/**
|
||||
* Get the (final) thing type version that the {@link Thing} will be updated to after all
|
||||
* {@link ThingUpdateInstruction}s with the same version have been applied.
|
||||
*
|
||||
* @return the thing-type version (always > 0)
|
||||
*/
|
||||
int getThingTypeVersion();
|
||||
|
||||
/**
|
||||
* Perform the update in this instruction for a given {@link Thing} using the given {@link ThingBuilder}
|
||||
* <p />
|
||||
* Note: the thing type version is not updated as there may be several instructions to perform for a single version.
|
||||
*
|
||||
* @param thing the thing that should be updated
|
||||
* @param thingBuilder the thing builder to use
|
||||
*/
|
||||
void perform(Thing thing, ThingBuilder thingBuilder);
|
||||
|
||||
/**
|
||||
* Check if this update is needed for a {@link Thing} with the given version
|
||||
*
|
||||
* @param currentThingTypeVersion the current thing type version of the {@link Thing}
|
||||
* @return <code>true</code> if this instruction should be applied, <code>false</code> otherwise
|
||||
*/
|
||||
static Predicate<ThingUpdateInstruction> applies(int currentThingTypeVersion) {
|
||||
return i -> i.getThingTypeVersion() > currentThingTypeVersion;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.thing.internal.update;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.internal.update.dto.AddChannel;
|
||||
import org.openhab.core.thing.internal.update.dto.InstructionSet;
|
||||
import org.openhab.core.thing.internal.update.dto.RemoveChannel;
|
||||
import org.openhab.core.thing.internal.update.dto.ThingType;
|
||||
import org.openhab.core.thing.internal.update.dto.UpdateChannel;
|
||||
import org.openhab.core.thing.internal.update.dto.UpdateDescriptions;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ThingUpdateInstructionReader} is used to read instructions for a given {@link ThingHandlerFactory} and
|
||||
* * create a list of {@link ThingUpdateInstruction}s
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ThingUpdateInstructionReader {
|
||||
private final Logger logger = LoggerFactory.getLogger(ThingUpdateInstructionReader.class);
|
||||
private final BundleResolver bundleResolver;
|
||||
|
||||
public ThingUpdateInstructionReader(BundleResolver bundleResolver) {
|
||||
this.bundleResolver = bundleResolver;
|
||||
}
|
||||
|
||||
public Map<UpdateInstructionKey, List<ThingUpdateInstruction>> readForFactory(ThingHandlerFactory factory) {
|
||||
Bundle bundle = bundleResolver.resolveBundle(factory.getClass());
|
||||
if (bundle == null) {
|
||||
logger.error(
|
||||
"Could not get bundle for '{}', thing type updates will fail. If this occurs outside of tests, it is a bug.",
|
||||
factory.getClass());
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
Map<UpdateInstructionKey, List<ThingUpdateInstruction>> updateInstructions = new HashMap<>();
|
||||
Enumeration<URL> entries = bundle.findEntries("OH-INF/update", "*.xml", true);
|
||||
if (entries != null) {
|
||||
while (entries.hasMoreElements()) {
|
||||
URL url = entries.nextElement();
|
||||
try {
|
||||
JAXBContext context = JAXBContext.newInstance(UpdateDescriptions.class);
|
||||
Unmarshaller u = context.createUnmarshaller();
|
||||
UpdateDescriptions updateDescriptions = (UpdateDescriptions) u.unmarshal(url);
|
||||
|
||||
for (ThingType thingType : updateDescriptions.getThingType()) {
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(thingType.getUid());
|
||||
UpdateInstructionKey key = new UpdateInstructionKey(factory, thingTypeUID);
|
||||
List<ThingUpdateInstruction> instructions = new ArrayList<>();
|
||||
List<InstructionSet> instructionSets = thingType.getInstructionSet().stream()
|
||||
.sorted(Comparator.comparing(InstructionSet::getTargetVersion)).toList();
|
||||
for (InstructionSet instructionSet : instructionSets) {
|
||||
int targetVersion = instructionSet.getTargetVersion();
|
||||
for (Object instruction : instructionSet.getInstructions()) {
|
||||
if (instruction instanceof AddChannel addChannelType) {
|
||||
instructions.add(new UpdateChannelInstructionImpl(targetVersion, addChannelType));
|
||||
} else if (instruction instanceof UpdateChannel updateChannelType) {
|
||||
instructions
|
||||
.add(new UpdateChannelInstructionImpl(targetVersion, updateChannelType));
|
||||
} else if (instruction instanceof RemoveChannel removeChannelType) {
|
||||
instructions.add(
|
||||
new RemoveChannelInstructionImpl(targetVersion, removeChannelType.getId()));
|
||||
} else {
|
||||
logger.warn("Instruction type '{}' is unknown.", instruction.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
updateInstructions.put(key, instructions);
|
||||
}
|
||||
logger.trace("Reading update instructions from '{}'", url.getPath());
|
||||
} catch (IllegalArgumentException | JAXBException e) {
|
||||
logger.warn("Failed to parse update instructions from '{}':", url, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updateInstructions;
|
||||
}
|
||||
|
||||
public record UpdateInstructionKey(ThingHandlerFactory factory, ThingTypeUID thingTypeId) {
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.thing.internal.update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.internal.update.dto.AddChannel;
|
||||
import org.openhab.core.thing.internal.update.dto.UpdateChannel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link UpdateChannelInstructionImpl} implements a {@link ThingUpdateInstruction} that updates a channel of a
|
||||
* thing.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UpdateChannelInstructionImpl implements ThingUpdateInstruction {
|
||||
private boolean removeOldChannel;
|
||||
private final int thingTypeVersion;
|
||||
private final boolean preserveConfig;
|
||||
private final String channelId;
|
||||
private final String channelTypeUid;
|
||||
private final @Nullable String label;
|
||||
private final @Nullable String description;
|
||||
private final @Nullable List<String> tags;
|
||||
|
||||
UpdateChannelInstructionImpl(int thingTypeVersion, UpdateChannel updateChannel) {
|
||||
this.removeOldChannel = true;
|
||||
this.thingTypeVersion = thingTypeVersion;
|
||||
this.channelId = updateChannel.getId();
|
||||
this.channelTypeUid = updateChannel.getType();
|
||||
this.label = updateChannel.getLabel();
|
||||
this.description = updateChannel.getDescription();
|
||||
this.tags = updateChannel.getTags();
|
||||
this.preserveConfig = updateChannel.isPreserveConfiguration();
|
||||
}
|
||||
|
||||
UpdateChannelInstructionImpl(int thingTypeVersion, AddChannel addChannel) {
|
||||
this.removeOldChannel = false;
|
||||
this.thingTypeVersion = thingTypeVersion;
|
||||
this.channelId = addChannel.getId();
|
||||
this.channelTypeUid = addChannel.getType();
|
||||
this.label = addChannel.getLabel();
|
||||
this.description = addChannel.getDescription();
|
||||
this.tags = addChannel.getTags();
|
||||
this.preserveConfig = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThingTypeVersion() {
|
||||
return thingTypeVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(Thing thing, ThingBuilder thingBuilder) {
|
||||
ChannelUID affectedChannelUid = new ChannelUID(thing.getUID(), channelId);
|
||||
|
||||
if (removeOldChannel) {
|
||||
thingBuilder.withoutChannel(affectedChannelUid);
|
||||
}
|
||||
|
||||
ChannelBuilder channelBuilder = ChannelBuilder.create(affectedChannelUid)
|
||||
.withType(new ChannelTypeUID(channelTypeUid));
|
||||
|
||||
if (preserveConfig) {
|
||||
Channel oldChannel = thing.getChannel(affectedChannelUid);
|
||||
if (oldChannel != null) {
|
||||
channelBuilder.withConfiguration(oldChannel.getConfiguration());
|
||||
channelBuilder.withDefaultTags(oldChannel.getDefaultTags());
|
||||
}
|
||||
}
|
||||
|
||||
if (label != null) {
|
||||
channelBuilder.withLabel(Objects.requireNonNull(label));
|
||||
}
|
||||
if (description != null) {
|
||||
channelBuilder.withDescription(Objects.requireNonNull(description));
|
||||
}
|
||||
if (tags != null) {
|
||||
channelBuilder.withDefaultTags(Set.copyOf(Objects.requireNonNull(tags)));
|
||||
}
|
||||
|
||||
thingBuilder.withChannel(channelBuilder.build());
|
||||
}
|
||||
}
|
@ -12,9 +12,12 @@
|
||||
*/
|
||||
package org.openhab.core.thing.type;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.registry.Identifiable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.thing.UID;
|
||||
|
||||
/**
|
||||
@ -29,23 +32,25 @@ import org.openhab.core.thing.UID;
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractDescriptionType implements Identifiable<UID> {
|
||||
|
||||
private UID uid;
|
||||
private String label;
|
||||
private @Nullable String description;
|
||||
private final UID uid;
|
||||
private final String label;
|
||||
private final @Nullable String description;
|
||||
private final @Nullable URI configDescriptionURI;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class with the specified parameters.
|
||||
*
|
||||
* @param uid the unique identifier which identifies the according type within
|
||||
* the overall system (must neither be null, nor empty)
|
||||
* @param label the human readable label for the according type
|
||||
* @param label the human-readable label for the according type
|
||||
* (must neither be null nor empty)
|
||||
* @param description the human readable description for the according type
|
||||
* @param description the human-readable description for the according type
|
||||
* (could be null or empty)
|
||||
* @param configDescriptionURI the {@link URI} that references the {@link ConfigDescription} of this type
|
||||
* @throws IllegalArgumentException if the UID is null, or the label is null or empty
|
||||
*/
|
||||
public AbstractDescriptionType(UID uid, String label, @Nullable String description)
|
||||
throws IllegalArgumentException {
|
||||
public AbstractDescriptionType(UID uid, String label, @Nullable String description,
|
||||
@Nullable URI configDescriptionURI) throws IllegalArgumentException {
|
||||
if (label.isEmpty()) {
|
||||
throw new IllegalArgumentException("The label must neither be null nor empty!");
|
||||
}
|
||||
@ -53,6 +58,7 @@ public abstract class AbstractDescriptionType implements Identifiable<UID> {
|
||||
this.uid = uid;
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
this.configDescriptionURI = configDescriptionURI;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,20 +73,29 @@ public abstract class AbstractDescriptionType implements Identifiable<UID> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human readable label for the according type.
|
||||
* Returns the human-readable label for the according type.
|
||||
*
|
||||
* @return the human readable label for the according type (neither null, nor empty)
|
||||
* @return the human-readable label for the according type (neither null, nor empty)
|
||||
*/
|
||||
public String getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human readable description for the according type.
|
||||
* Returns the human-readable description for the according type.
|
||||
*
|
||||
* @return the human readable description for the according type (could be null or empty)
|
||||
* @return the human-readable description for the according type (could be null or empty)
|
||||
*/
|
||||
public @Nullable String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to a concrete {@link ConfigDescription}.
|
||||
*
|
||||
* @return the link to a concrete ConfigDescription
|
||||
*/
|
||||
public @Nullable URI getConfigDescriptionURI() {
|
||||
return configDescriptionURI;
|
||||
}
|
||||
}
|
||||
|
@ -38,15 +38,15 @@ public class ChannelGroupType extends AbstractDescriptionType {
|
||||
* Creates a new instance of this class with the specified parameters.
|
||||
*
|
||||
* @param uid the unique identifier which identifies this channel group type within the
|
||||
* @param label the human readable label for the according type
|
||||
* @param description the human readable description for the according type
|
||||
* @param label the human-readable label for the according type
|
||||
* @param description the human-readable description for the according type
|
||||
* @param category the category of this channel group type, e.g. Temperature
|
||||
* @param channelDefinitions the channel definitions this channel group forms
|
||||
* @throws IllegalArgumentException if the UID is null, or the label is null or empty
|
||||
*/
|
||||
ChannelGroupType(ChannelGroupTypeUID uid, String label, @Nullable String description, @Nullable String category,
|
||||
@Nullable List<ChannelDefinition> channelDefinitions) throws IllegalArgumentException {
|
||||
super(uid, label, description);
|
||||
super(uid, label, description, null);
|
||||
|
||||
this.category = category;
|
||||
this.channelDefinitions = channelDefinitions == null ? Collections.emptyList()
|
||||
|
@ -17,14 +17,13 @@ import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.CommandDescription;
|
||||
import org.openhab.core.types.EventDescription;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
|
||||
/**
|
||||
* The {@link ChannelType} describes a concrete type of a {@link Channel}.
|
||||
* The {@link ChannelType} describes a concrete type of {@link Channel}.
|
||||
* <p>
|
||||
* This description is used as template definition for the creation of the according concrete {@link Channel} object.
|
||||
* Use the {@link ChannelTypeBuilder} for building channel types.
|
||||
@ -45,7 +44,6 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
private final @Nullable StateDescription state;
|
||||
private final @Nullable CommandDescription commandDescription;
|
||||
private final @Nullable EventDescription event;
|
||||
private final @Nullable URI configDescriptionURI;
|
||||
private final @Nullable AutoUpdatePolicy autoUpdatePolicy;
|
||||
|
||||
/**
|
||||
@ -56,15 +54,15 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
* @param advanced true if this channel type contains advanced features, otherwise false
|
||||
* @param itemType the item type of this Channel type, e.g. {@code ColorItem}
|
||||
* @param kind the channel kind.
|
||||
* @param label the human readable label for the according type
|
||||
* @param label the human-readable label for the according type
|
||||
* (must neither be null nor empty)
|
||||
* @param description the human readable description for the according type
|
||||
* @param description the human-readable description for the according type
|
||||
* (could be null or empty)
|
||||
* @param category the category of this Channel type, e.g. {@code TEMPERATURE} (could be null or empty)
|
||||
* @param tags all tags of this {@link ChannelType}, e.g. {@code Alarm} (could be null or empty)
|
||||
* @param state a {@link StateDescription} of an items state which gives information how to interpret it.
|
||||
* @param commandDescription a {@link CommandDescription} which should be rendered as push-buttons. The command
|
||||
* values will be send to the channel from this {@link ChannelType}.
|
||||
* values will be sent to the channel from this {@link ChannelType}.
|
||||
* @param configDescriptionURI the link to the concrete ConfigDescription (could be null)
|
||||
* @param autoUpdatePolicy the {@link AutoUpdatePolicy} to use.
|
||||
* @throws IllegalArgumentException if the UID or the item type is null or empty,
|
||||
@ -75,7 +73,7 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
@Nullable StateDescription state, @Nullable CommandDescription commandDescription,
|
||||
@Nullable EventDescription event, @Nullable URI configDescriptionURI,
|
||||
@Nullable AutoUpdatePolicy autoUpdatePolicy) throws IllegalArgumentException {
|
||||
super(uid, label, description);
|
||||
super(uid, label, description, configDescriptionURI);
|
||||
|
||||
if (kind == ChannelKind.STATE && (itemType == null || itemType.isBlank())) {
|
||||
throw new IllegalArgumentException("If the kind is 'state', the item type must be set!");
|
||||
@ -86,7 +84,6 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
|
||||
this.itemType = itemType;
|
||||
this.kind = kind;
|
||||
this.configDescriptionURI = configDescriptionURI;
|
||||
|
||||
this.tags = tags == null ? Set.of() : Set.copyOf(tags);
|
||||
this.advanced = advanced;
|
||||
@ -135,15 +132,6 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
return super.getUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to a concrete {@link ConfigDescription}.
|
||||
*
|
||||
* @return the link to a concrete ConfigDescription
|
||||
*/
|
||||
public @Nullable URI getConfigDescriptionURI() {
|
||||
return this.configDescriptionURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link StateDescription} of an items state which gives information how to interpret it.
|
||||
*
|
||||
@ -154,7 +142,7 @@ public class ChannelType extends AbstractDescriptionType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns informations about the supported events.
|
||||
* Returns information about the supported events.
|
||||
*
|
||||
* @return the event information. Can be null if the channel is a state channel.
|
||||
*/
|
||||
|
@ -19,12 +19,11 @@ import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ThingType} describes a concrete type of a {@link Thing}.
|
||||
* The {@link ThingType} describes a concrete type of {@link Thing}.
|
||||
* <p>
|
||||
* This description is used as template definition for the creation of the according concrete {@link Thing} object.
|
||||
* <p>
|
||||
@ -46,7 +45,6 @@ public class ThingType extends AbstractDescriptionType {
|
||||
private final List<String> supportedBridgeTypeUIDs;
|
||||
private final Map<String, String> properties;
|
||||
private final @Nullable String representationProperty;
|
||||
private final @Nullable URI configDescriptionURI;
|
||||
private final boolean listed;
|
||||
private final @Nullable String category;
|
||||
|
||||
@ -57,9 +55,9 @@ public class ThingType extends AbstractDescriptionType {
|
||||
* (must neither be null, nor empty)
|
||||
* @param supportedBridgeTypeUIDs the unique identifiers of the bridges this Thing type supports
|
||||
* (could be null or empty)
|
||||
* @param label the human readable label for the according type
|
||||
* @param label the human-readable label for the according type
|
||||
* (must neither be null nor empty)
|
||||
* @param description the human readable description for the according type
|
||||
* @param description the human-readable description for the according type
|
||||
* (could be null or empty)
|
||||
* @param listed determines whether it should be listed for manually pairing or not
|
||||
* @param representationProperty name of the property that uniquely identifies this Thing
|
||||
@ -69,7 +67,7 @@ public class ThingType extends AbstractDescriptionType {
|
||||
* @param properties the properties this Thing type provides (could be null)
|
||||
* @param configDescriptionURI the link to the concrete ConfigDescription (could be null)
|
||||
* @param extensibleChannelTypeIds the channel-type ids this thing-type is extensible with (could be null or empty).
|
||||
* @throws IllegalArgumentException if the UID is null or empty, or the the meta information is null
|
||||
* @throws IllegalArgumentException if the UID is null or empty, or the meta information is null
|
||||
*/
|
||||
ThingType(ThingTypeUID uid, @Nullable List<String> supportedBridgeTypeUIDs, String label,
|
||||
@Nullable String description, @Nullable String category, boolean listed,
|
||||
@ -77,7 +75,7 @@ public class ThingType extends AbstractDescriptionType {
|
||||
@Nullable List<ChannelGroupDefinition> channelGroupDefinitions, @Nullable Map<String, String> properties,
|
||||
@Nullable URI configDescriptionURI, @Nullable List<String> extensibleChannelTypeIds)
|
||||
throws IllegalArgumentException {
|
||||
super(uid, label, description);
|
||||
super(uid, label, description, configDescriptionURI);
|
||||
|
||||
this.category = category;
|
||||
this.listed = listed;
|
||||
@ -91,7 +89,6 @@ public class ThingType extends AbstractDescriptionType {
|
||||
this.extensibleChannelTypeIds = extensibleChannelTypeIds == null ? Collections.emptyList()
|
||||
: Collections.unmodifiableList(extensibleChannelTypeIds);
|
||||
this.properties = properties == null ? Collections.emptyMap() : Collections.unmodifiableMap(properties);
|
||||
this.configDescriptionURI = configDescriptionURI;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,15 +145,6 @@ public class ThingType extends AbstractDescriptionType {
|
||||
return this.channelGroupDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to a concrete {@link ConfigDescription}.
|
||||
*
|
||||
* @return the link to a concrete ConfigDescription (could be null)
|
||||
*/
|
||||
public @Nullable URI getConfigDescriptionURI() {
|
||||
return this.configDescriptionURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties for this {@link ThingType}
|
||||
*
|
||||
|
@ -0,0 +1,34 @@
|
||||
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema" jaxb:version="2.0">
|
||||
<jaxb:globalBindings>
|
||||
<xjc:serializable uid="1"/>
|
||||
</jaxb:globalBindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="update-description-1.0.0.xsd">
|
||||
<jaxb:schemaBindings>
|
||||
<jaxb:package name="org.openhab.core.thing.internal.update.dto"/>
|
||||
</jaxb:schemaBindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
<jaxb:bindings node="//xs:complexType[@name='parameter']">
|
||||
<jaxb:bindings node=".//xs:attribute[@name='required']">
|
||||
<jaxb:property name="RequiredAttribute"/>
|
||||
</jaxb:bindings>
|
||||
</jaxb:bindings>
|
||||
<jaxb:bindings node="//xs:complexType[@name='optionsType']">
|
||||
<jaxb:bindings node=".//xs:attribute[@name='value']">
|
||||
<jaxb:property name="ValueAttribute"/>
|
||||
</jaxb:bindings>
|
||||
</jaxb:bindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
<jaxb:bindings schemaLocation="https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<jaxb:bindings node="//xs:complexType[@name='option']">
|
||||
<jaxb:bindings node=".//xs:attribute[@name='value']">
|
||||
<jaxb:property name="ValueAttribute"/>
|
||||
</jaxb:bindings>
|
||||
</jaxb:bindings>
|
||||
</jaxb:bindings>
|
||||
|
||||
</jaxb:bindings>
|
@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
|
||||
xmlns:update-description="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xmlns:thing-description="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
targetNamespace="https://openhab.org/schemas/update-description/v1.0.0">
|
||||
|
||||
<xs:import namespace="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
schemaLocation="https://openhab.org/schemas/config-description-1.0.0.xsd"/>
|
||||
<xs:import namespace="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
schemaLocation="https://openhab.org/schemas/thing-description-1.0.0.xsd"/>
|
||||
|
||||
<xs:element name="update-descriptions">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The root element of an update description. It contains the update instructions for managed
|
||||
things after thing-type changes. Instructions are grouped by thing-type and (internal) version.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="thing-type" type="update-description:thingType" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="thingType">
|
||||
<xs:sequence>
|
||||
<xs:element name="instruction-set" type="update-description:instructionSet" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="uid" type="update-description:thingTypeUid" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="instructionSet">
|
||||
<xs:choice maxOccurs="unbounded">
|
||||
<xs:annotation>
|
||||
<xs:appinfo>
|
||||
<jaxb:property name="instructions"/>
|
||||
</xs:appinfo>
|
||||
</xs:annotation>
|
||||
<xs:element name="add-channel" type="update-description:addChannel" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="update-channel" type="update-description:updateChannel" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
<xs:element name="remove-channel" type="update-description:removeChannel" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xs:choice>
|
||||
<xs:attribute name="targetVersion" type="xs:int" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="addChannel">
|
||||
<xs:sequence>
|
||||
<xs:element name="type" type="update-description:channelTypeUid"/>
|
||||
<xs:element name="label" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="description" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="tags" type="thing-description:tags" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="id" type="update-description:channelId" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="updateChannel">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Update a channel (e.g. change channel-type, label, description). By default, the old
|
||||
configuration and tags are copied to the new channel.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:sequence>
|
||||
<xs:element name="type" type="update-description:channelTypeUid"/>
|
||||
<xs:element name="label" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="description" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="tags" type="thing-description:tags" minOccurs="0">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If set, already existing tags are overwritten.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="id" type="update-description:channelId" use="required"/>
|
||||
<xs:attribute name="preserveConfiguration" type="xs:boolean" default="true"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="removeChannel">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Remove a channel from a thing.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:attribute name="id" type="update-description:channelId" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="channelId">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The simple id of the channel (i.e. without the thing UID).</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="config-description:idRestrictionPattern"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="channelTypeUid">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The fully qualified UID of the channel type (e.g. "system:color",
|
||||
"viessmann:lastErrorMessage").
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="config-description:uriRestrictionPattern"/>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="thingTypeUid">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The fully qualified UID of the thing type.</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:restriction base="config-description:uriRestrictionPattern"/>
|
||||
</xs:simpleType>
|
||||
|
||||
</xs:schema>
|
@ -49,7 +49,7 @@ import org.openhab.core.types.StateOption;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* {@link ThingEventFactoryTests} tests the {@link ThingEventFactory}.
|
||||
* {@link ThingEventFactoryTest} tests the {@link ThingEventFactory}.
|
||||
*
|
||||
* @author Stefan Bußweiler - Initial contribution
|
||||
* @author Christoph Weitkamp - Added ChannelStateDescriptionChangedEvent
|
||||
|
@ -28,6 +28,7 @@ import org.openhab.core.common.SafeCaller;
|
||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.validation.ConfigDescriptionValidator;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
@ -43,9 +44,10 @@ import org.openhab.core.thing.internal.ThingTracker.ThingTrackerEvent;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
/**
|
||||
* @author Simon Kaufmann - Initial contribution
|
||||
@ -55,8 +57,6 @@ import org.osgi.framework.Bundle;
|
||||
@NonNullByDefault
|
||||
public class ThingManagerImplTest extends JavaTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) Bundle bundleMock;
|
||||
private @Mock @NonNullByDefault({}) BundleResolver bundleResolverMock;
|
||||
private @Mock @NonNullByDefault({}) ChannelGroupTypeRegistry channelGroupTypeRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) ChannelTypeRegistry channelTypeRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) CommunicationManager communicationManagerMock;
|
||||
@ -71,24 +71,29 @@ public class ThingManagerImplTest extends JavaTest {
|
||||
private @Mock @NonNullByDefault({}) StorageService storageServiceMock;
|
||||
private @Mock @NonNullByDefault({}) Thing thingMock;
|
||||
private @Mock @NonNullByDefault({}) ThingRegistryImpl thingRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) BundleResolver bundleResolverMock;
|
||||
private @Mock @NonNullByDefault({}) TranslationProvider translationProviderMock;
|
||||
private @Mock @NonNullByDefault({}) BundleContext bundleContextMock;
|
||||
private @Mock @NonNullByDefault({}) ThingType thingTypeMock;
|
||||
|
||||
// This class is final so it cannot be mocked
|
||||
private final ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService = new ThingStatusInfoI18nLocalizationService();
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(bundleMock.getSymbolicName()).thenReturn("test");
|
||||
when(bundleResolverMock.resolveBundle(any())).thenReturn(bundleMock);
|
||||
when(thingMock.getUID()).thenReturn(new ThingUID("test", "thing"));
|
||||
when(thingMock.getStatusInfo())
|
||||
.thenReturn(new ThingStatusInfo(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE, null));
|
||||
when(thingTypeMock.getConfigDescriptionURI()).thenReturn(null);
|
||||
when(thingTypeRegistryMock.getThingType(any())).thenReturn(thingTypeMock);
|
||||
}
|
||||
|
||||
private ThingManagerImpl createThingManager() {
|
||||
return new ThingManagerImpl(bundleResolverMock, channelGroupTypeRegistryMock, channelTypeRegistryMock,
|
||||
communicationManagerMock, configDescriptionRegistryMock, configDescriptionValidatorMock,
|
||||
eventPublisherMock, itemChannelLinkRegistryMock, readyServiceMock, safeCallerMock, storageServiceMock,
|
||||
thingRegistryMock, thingStatusInfoI18nLocalizationService, thingTypeRegistryMock);
|
||||
return new ThingManagerImpl(channelGroupTypeRegistryMock, channelTypeRegistryMock, communicationManagerMock,
|
||||
configDescriptionRegistryMock, configDescriptionValidatorMock, eventPublisherMock,
|
||||
itemChannelLinkRegistryMock, readyServiceMock, safeCallerMock, storageServiceMock, thingRegistryMock,
|
||||
thingStatusInfoI18nLocalizationService, thingTypeRegistryMock, bundleResolverMock,
|
||||
translationProviderMock, bundleContextMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -166,7 +166,7 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// check getBridge works
|
||||
assertThat(getBridge().getUID().toString(), is("bindingId:type1:bridgeId"));
|
||||
assertThat(getBridge().getUID().toString(), is(BINDING_ID + ":type1:bridgeId"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,12 +206,17 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
ThingTypeUID bridgeTypeUID = new ThingTypeUID("bindingId:type1");
|
||||
ThingUID bridgeUID = new ThingUID("bindingId:type1:bridgeId");
|
||||
ThingTypeUID bridgeTypeUID = new ThingTypeUID(BINDING_ID, "type1");
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, "type2");
|
||||
|
||||
ThingType bridgeType = ThingTypeBuilder.instance(bridgeTypeUID, "bridge").buildBridge();
|
||||
ThingType thingType = ThingTypeBuilder.instance(thingTypeUID, "thing").build();
|
||||
registerThingTypeProvider(bridgeType, thingType);
|
||||
|
||||
ThingUID bridgeUID = new ThingUID(BINDING_ID, "type1", "bridgeId");
|
||||
Bridge bridge = BridgeBuilder.create(bridgeTypeUID, bridgeUID).build();
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type2");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type2:thingId");
|
||||
ThingUID thingUID = new ThingUID(BINDING_ID, "type2", "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).withBridge(bridge.getUID()).build();
|
||||
|
||||
managedThingProvider.add(bridge);
|
||||
@ -234,7 +239,7 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
});
|
||||
|
||||
// the assertion is in handle command
|
||||
handler.handleCommand(new ChannelUID("bindingId:type2:thingId:channel"), RefreshType.REFRESH);
|
||||
handler.handleCommand(new ChannelUID(thingUID, "thingId", "channel"), RefreshType.REFRESH);
|
||||
|
||||
unregisterService(ThingHandlerFactory.class.getName());
|
||||
thingHandlerFactory.deactivate(componentContextMock);
|
||||
@ -245,10 +250,12 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
ConfigStatusProviderThingHandlerFactory thingHandlerFactory = new ConfigStatusProviderThingHandlerFactory();
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID)
|
||||
.withConfiguration(new Configuration(Map.of("parameter", ""))).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -301,10 +308,12 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
ConfigStatusProviderThingHandlerFactory thingHandlerFactory = new ConfigStatusProviderThingHandlerFactory();
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID)
|
||||
.withConfiguration(new Configuration(Map.of("parameter", "ok"))).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -329,30 +338,30 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
thing.getHandler().handleConfigurationUpdate(Map.of("param", "invalid"));
|
||||
thing.getHandler().handleConfigurationUpdate(Map.of("parameter", "invalid"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
Event event = eventSubscriber.getReceivedEvent();
|
||||
assertThat(event, is(notNullValue()));
|
||||
assertThat(event.getPayload(), CoreMatchers
|
||||
.containsString("\"parameterName\":\"param\",\"type\":\"ERROR\",\"message\":\"param invalid\"}"));
|
||||
assertThat(event.getPayload(), CoreMatchers.containsString(
|
||||
"\"parameterName\":\"parameter\",\"type\":\"ERROR\",\"message\":\"param invalid\"}"));
|
||||
eventSubscriber.resetReceivedEvent();
|
||||
}, 2500, DFL_SLEEP_TIME);
|
||||
|
||||
thing.getHandler().handleConfigurationUpdate(Map.of("param", "ok"));
|
||||
thing.getHandler().handleConfigurationUpdate(Map.of("parameter", "ok"));
|
||||
|
||||
waitForAssert(() -> {
|
||||
Event event = eventSubscriber.getReceivedEvent();
|
||||
assertThat(event, is(notNullValue()));
|
||||
assertThat(event.getPayload(), CoreMatchers
|
||||
.containsString("\"parameterName\":\"param\",\"type\":\"INFORMATION\",\"message\":\"param ok\"}"));
|
||||
assertThat(event.getPayload(), CoreMatchers.containsString(
|
||||
"\"parameterName\":\"parameter\",\"type\":\"INFORMATION\",\"message\":\"param ok\"}"));
|
||||
}, 2500, DFL_SLEEP_TIME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertBaseThingHandlerNotifiesThingManagerAboutConfigurationUpdates() {
|
||||
// register ThingTypeProvider & ConfigurationDescription with 'required' parameter
|
||||
registerThingTypeProvider();
|
||||
registerDefaultThingTypeProvider();
|
||||
registerConfigDescriptionProvider(true);
|
||||
|
||||
// register thing handler factory
|
||||
@ -396,7 +405,7 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
|
||||
static class ConfigStatusProviderThingHandler extends ConfigStatusThingHandler {
|
||||
|
||||
private static final String PARAM = "param";
|
||||
private static final String PARAM = "parameter";
|
||||
private static final ConfigStatusMessage ERROR = ConfigStatusMessage.Builder.error(PARAM)
|
||||
.withMessageKeySuffix("param.invalid").build();
|
||||
private static final ConfigStatusMessage INFO = ConfigStatusMessage.Builder.information(PARAM)
|
||||
@ -448,7 +457,7 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
public void initialize() {
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
thingBuilder.withChannel(
|
||||
ChannelBuilder.create(new ChannelUID("bindingId:type:thingId:1"), CoreItemFactory.STRING).build());
|
||||
ChannelBuilder.create(new ChannelUID(thing.getUID(), "1"), CoreItemFactory.STRING).build());
|
||||
updateThing(thingBuilder.build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
@ -493,18 +502,21 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@Test
|
||||
public void assertThingCanBeUpdatedFromThingHandler() {
|
||||
registerThingTypeProvider();
|
||||
registerDefaultThingTypeProvider();
|
||||
YetAnotherThingHandlerFactory thingHandlerFactory = new YetAnotherThingHandlerFactory();
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
final ThingRegistryChangeListener listener = new ThingRegistryChangeListener();
|
||||
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
try {
|
||||
thingRegistry.addRegistryChangeListener(listener);
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID)
|
||||
.withConfiguration(new Configuration(Map.of("parameter", ""))).build();
|
||||
assertThat(thing.getChannels().size(), is(0));
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -522,17 +534,20 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@Test
|
||||
public void assertPropertiesCanBeUpdatedFromThingHandler() {
|
||||
registerThingTypeProvider();
|
||||
registerDefaultThingTypeProvider();
|
||||
YetAnotherThingHandlerFactory thingHandlerFactory = new YetAnotherThingHandlerFactory();
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
final ThingRegistryChangeListener listener = new ThingRegistryChangeListener();
|
||||
|
||||
try {
|
||||
thingRegistry.addRegistryChangeListener(listener);
|
||||
Thing thing = ThingBuilder
|
||||
.create(new ThingTypeUID("bindingId:type"), new ThingUID("bindingId:type:thingId")).build();
|
||||
.create(new ThingTypeUID(BINDING_ID, THING_TYPE_ID),
|
||||
new ThingUID(BINDING_ID, THING_TYPE_ID, "thingId"))
|
||||
.withConfiguration(new Configuration(Map.of("parameter", ""))).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -577,10 +592,12 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
|
||||
final ThingRegistryChangeListener listener = new ThingRegistryChangeListener();
|
||||
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
try {
|
||||
thingRegistry.addRegistryChangeListener(listener);
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
@ -601,11 +618,11 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(true);
|
||||
registerDefaultThingTypeProvider();
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID)
|
||||
.withConfiguration(new Configuration(Map.of("parameter", "someValue"))).build();
|
||||
|
||||
@ -624,11 +641,11 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerDefaultThingTypeProvider();
|
||||
registerConfigDescriptionProvider(true);
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID)
|
||||
.withConfiguration(new Configuration(Map.of("parameter", "someValue"))).build();
|
||||
|
||||
@ -658,12 +675,12 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
registerThingTypeAndConfigDescription();
|
||||
registerDefaultThingTypeAndConfigDescription();
|
||||
|
||||
ThingRegistry thingRegistry = getService(ThingRegistry.class);
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID("bindingId:type");
|
||||
ThingUID thingUID = new ThingUID("bindingId:type:thingId");
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_ID);
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, "thingId");
|
||||
Thing thing = ThingBuilder.create(thingTypeUID, thingUID).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
@ -694,13 +711,17 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactory.activate(componentContextMock);
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
ThingTypeUID thingType1 = new ThingTypeUID("bindingId:type1");
|
||||
ThingTypeUID thingType2 = new ThingTypeUID("bindingId:type2");
|
||||
ThingTypeUID thingTypeUID1 = new ThingTypeUID(BINDING_ID, "type1");
|
||||
ThingTypeUID thingTypeUID2 = new ThingTypeUID(BINDING_ID, "type2");
|
||||
|
||||
Bridge bridge = BridgeBuilder.create(thingType1, new ThingUID("bindingId:type1:bridgeId")).build();
|
||||
Thing thingA = ThingBuilder.create(thingType2, new ThingUID("bindingId:type2:thingIdA"))
|
||||
ThingType thingType1 = ThingTypeBuilder.instance(thingTypeUID1, thingTypeUID1.getId()).build();
|
||||
ThingType thingType2 = ThingTypeBuilder.instance(thingTypeUID2, thingTypeUID2.getId()).build();
|
||||
registerThingTypeProvider(thingType1, thingType2);
|
||||
|
||||
Bridge bridge = BridgeBuilder.create(thingTypeUID1, new ThingUID(thingTypeUID1, "bridgeId")).build();
|
||||
Thing thingA = ThingBuilder.create(thingTypeUID2, new ThingUID(thingTypeUID2, "thingIdA"))
|
||||
.withBridge(bridge.getUID()).build();
|
||||
Thing thingB = ThingBuilder.create(thingType2, new ThingUID("bindingId:type2:thingIdB"))
|
||||
Thing thingB = ThingBuilder.create(thingTypeUID2, new ThingUID(thingTypeUID2, "thingIdB"))
|
||||
.withBridge(bridge.getUID()).build();
|
||||
|
||||
assertThat(bridge.getStatus(), is(ThingStatus.UNINITIALIZED));
|
||||
@ -751,40 +772,35 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerThingTypeAndConfigDescription() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(new ThingTypeUID(BINDING_ID, THING_TYPE_ID), "label")
|
||||
.withConfigDescriptionURI(BINDING_CONFIG_URI).build();
|
||||
private void registerDefaultThingTypeAndConfigDescription() {
|
||||
registerDefaultThingTypeProvider();
|
||||
ConfigDescription configDescription = ConfigDescriptionBuilder.create(BINDING_CONFIG_URI)
|
||||
.withParameter(ConfigDescriptionParameterBuilder
|
||||
.create("parameter", ConfigDescriptionParameter.Type.TEXT).withRequired(true).build())
|
||||
.build();
|
||||
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(thingTypeProvider.getThingType(ArgumentMatchers.any(ThingTypeUID.class),
|
||||
ArgumentMatchers.any(Locale.class))).thenReturn(thingType);
|
||||
registerService(thingTypeProvider);
|
||||
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
when(thingTypeRegistry.getThingType(ArgumentMatchers.any(ThingTypeUID.class))).thenReturn(thingType);
|
||||
registerService(thingTypeRegistry);
|
||||
|
||||
ConfigDescriptionProvider configDescriptionProvider = mock(ConfigDescriptionProvider.class);
|
||||
when(configDescriptionProvider.getConfigDescription(ArgumentMatchers.any(URI.class),
|
||||
ArgumentMatchers.nullable(Locale.class))).thenReturn(configDescription);
|
||||
when(configDescriptionProvider.getConfigDescription(eq(BINDING_CONFIG_URI), nullable(Locale.class)))
|
||||
.thenReturn(configDescription);
|
||||
registerService(configDescriptionProvider);
|
||||
}
|
||||
|
||||
private void registerThingTypeProvider() {
|
||||
private void registerDefaultThingTypeProvider() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(new ThingTypeUID(BINDING_ID, THING_TYPE_ID), "label")
|
||||
.withConfigDescriptionURI(BINDING_CONFIG_URI).build();
|
||||
registerThingTypeProvider(thingType);
|
||||
}
|
||||
|
||||
private void registerThingTypeProvider(ThingType... thingTypes) {
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(thingTypeProvider.getThingType(ArgumentMatchers.any(ThingTypeUID.class),
|
||||
ArgumentMatchers.nullable(Locale.class))).thenReturn(thingType);
|
||||
registerService(thingTypeProvider);
|
||||
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
when(thingTypeRegistry.getThingType(ArgumentMatchers.any(ThingTypeUID.class))).thenReturn(thingType);
|
||||
|
||||
for (ThingType thingType : thingTypes) {
|
||||
when(thingTypeProvider.getThingType(eq(thingType.getUID()), nullable(Locale.class))).thenReturn(thingType);
|
||||
when(thingTypeRegistry.getThingType(eq(thingType.getUID()))).thenReturn(thingType);
|
||||
}
|
||||
|
||||
registerService(thingTypeProvider);
|
||||
registerService(thingTypeRegistry);
|
||||
}
|
||||
|
||||
@ -796,8 +812,8 @@ public class BindingBaseClassesOSGiTest extends JavaOSGiTest {
|
||||
.build();
|
||||
|
||||
ConfigDescriptionProvider configDescriptionProvider = mock(ConfigDescriptionProvider.class);
|
||||
when(configDescriptionProvider.getConfigDescription(ArgumentMatchers.any(URI.class),
|
||||
ArgumentMatchers.nullable(Locale.class))).thenReturn(configDescription);
|
||||
when(configDescriptionProvider.getConfigDescription(ArgumentMatchers.any(URI.class), nullable(Locale.class)))
|
||||
.thenReturn(configDescription);
|
||||
registerService(configDescriptionProvider);
|
||||
}
|
||||
}
|
||||
|
@ -437,13 +437,17 @@ public class ChangeThingTypeOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
private List<ChannelDefinition> getChannelDefinitions(ThingTypeUID thingTypeUID) throws URISyntaxException {
|
||||
URI configDescriptionUri = new URI("scheme", "channelType:" + thingTypeUID.getId(), null);
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID("test:" + thingTypeUID.getId());
|
||||
ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, "channelLabel", "itemType")
|
||||
.withDescription("description") //
|
||||
.withCategory("category") //
|
||||
.withConfigDescriptionURI(new URI("scheme", "channelType:" + thingTypeUID.getId(), null)).build();
|
||||
.withConfigDescriptionURI(configDescriptionUri).build();
|
||||
channelTypes.put(channelTypeUID, channelType);
|
||||
|
||||
ConfigDescription configDescription = ConfigDescriptionBuilder.create(configDescriptionUri).build();
|
||||
configDescriptions.put(configDescriptionUri, configDescription);
|
||||
|
||||
ChannelDefinition cd = new ChannelDefinitionBuilder("channel" + thingTypeUID.getId(), channelTypeUID).build();
|
||||
return List.of(cd);
|
||||
}
|
||||
|
@ -39,9 +39,13 @@ import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
|
||||
import org.openhab.core.thing.testutil.i18n.DefaultLocaleSetter;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
import org.osgi.framework.Bundle;
|
||||
@ -88,6 +92,8 @@ public class ThingStatusInfoI18nLocalizationServiceOSGiTest extends JavaOSGiTest
|
||||
simpleThingHandlerFactory.activate(componentContext);
|
||||
registerService(simpleThingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
|
||||
registerThingType();
|
||||
|
||||
thing = ThingBuilder.create(new ThingTypeUID("aaa:bbb"), "ccc").build();
|
||||
|
||||
managedThingProvider = getService(ManagedThingProvider.class);
|
||||
@ -330,4 +336,17 @@ public class ThingStatusInfoI18nLocalizationServiceOSGiTest extends JavaOSGiTest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerThingType() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(new ThingTypeUID("aaa:bbb"), "label").build();
|
||||
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
|
||||
when(thingTypeProvider.getThingType(eq(thingType.getUID()), nullable(Locale.class))).thenReturn(thingType);
|
||||
when(thingTypeRegistry.getThingType(eq(thingType.getUID()))).thenReturn(thingType);
|
||||
|
||||
registerService(thingTypeProvider);
|
||||
registerService(thingTypeRegistry);
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,15 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -38,6 +41,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
@ -59,6 +64,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.events.AbstractThingRegistryEvent;
|
||||
@ -71,7 +77,14 @@ import org.openhab.core.thing.link.dto.ItemChannelLinkDTO;
|
||||
import org.openhab.core.thing.link.events.AbstractItemChannelLinkRegistryEvent;
|
||||
import org.openhab.core.thing.link.events.ItemChannelLinkRemovedEvent;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.thing.util.ThingHandlerHelper;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
@ -85,6 +98,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Wouter Born - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class ChannelLinkNotifierOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@ -162,6 +176,7 @@ public class ChannelLinkNotifierOSGiTest extends JavaOSGiTest {
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
registerVolatileStorageService();
|
||||
registerThingAndChannelTypeProvider();
|
||||
|
||||
itemChannelLinkRegistry = getService(ItemChannelLinkRegistry.class);
|
||||
assertThat(itemChannelLinkRegistry, is(notNullValue()));
|
||||
@ -184,14 +199,8 @@ public class ChannelLinkNotifierOSGiTest extends JavaOSGiTest {
|
||||
when(thingHandlerFactoryMock.supportsThingType(eq(THING_TYPE_UID))).thenReturn(true);
|
||||
registerService(thingHandlerFactoryMock);
|
||||
|
||||
when(bundleMock.getSymbolicName()).thenReturn("org.openhab.core.thing");
|
||||
when(bundleResolverMock.resolveBundle(any())).thenReturn(bundleMock);
|
||||
|
||||
ThingManagerImpl thingManager = (ThingManagerImpl) getService(ThingManager.class);
|
||||
assertThat(thingManager, is(notNullValue()));
|
||||
if (thingManager != null) {
|
||||
thingManager.setBundleResolver(bundleResolverMock);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@ -560,4 +569,27 @@ public class ChannelLinkNotifierOSGiTest extends JavaOSGiTest {
|
||||
assertNoChannelLinkEventsReceived(subjectThing);
|
||||
assertNoChannelLinkEventsReceived(otherThing);
|
||||
}
|
||||
|
||||
private void registerThingAndChannelTypeProvider() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(THING_TYPE_UID, "label").build();
|
||||
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(thingTypeProvider.getThingType(any(ThingTypeUID.class), nullable(Locale.class))).thenReturn(thingType);
|
||||
registerService(thingTypeProvider);
|
||||
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
when(thingTypeRegistry.getThingType(any(ThingTypeUID.class))).thenReturn(thingType);
|
||||
registerService(thingTypeRegistry);
|
||||
|
||||
ChannelType channelType = ChannelTypeBuilder.state(CHANNEL_TYPE_UID, "Number", "Number").build();
|
||||
|
||||
ChannelTypeProvider channelTypeProvider = mock(ChannelTypeProvider.class);
|
||||
when(channelTypeProvider.getChannelType(any(ChannelTypeUID.class), nullable(Locale.class)))
|
||||
.thenReturn(channelType);
|
||||
registerService(channelTypeProvider);
|
||||
|
||||
ChannelTypeRegistry channelTypeRegistry = mock(ChannelTypeRegistry.class);
|
||||
when(channelTypeRegistry.getChannelType(any(ChannelTypeUID.class))).thenReturn(channelType);
|
||||
registerService(channelTypeRegistry);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Semaphore;
|
||||
@ -112,6 +113,8 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
CHANNEL_GROUP_ID);
|
||||
|
||||
private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID("binding:thing");
|
||||
private static final ThingTypeUID THING_WITH_BRIDGE_TYPE_UID = new ThingTypeUID("binding:thingWithBridge");
|
||||
|
||||
private static final ThingUID THING_UID = new ThingUID(THING_TYPE_UID, "thing");
|
||||
|
||||
private static final ThingTypeUID BRIDGE_TYPE_UID = new ThingTypeUID("binding:bridge");
|
||||
@ -127,6 +130,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
private @NonNullByDefault({}) ThingRegistry thingRegistry;
|
||||
private @NonNullByDefault({}) ReadyService readyService;
|
||||
private @NonNullByDefault({}) Storage<Boolean> storage;
|
||||
|
||||
private @NonNullByDefault({}) ThingManager thingManager;
|
||||
|
||||
private @NonNullByDefault({}) URI configDescriptionChannel;
|
||||
@ -137,6 +141,12 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
public void setUp() throws Exception {
|
||||
configDescriptionChannel = new URI("test:channel");
|
||||
configDescriptionThing = new URI("test:test");
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerChannelTypeProvider();
|
||||
registerChannelGroupTypeProvider();
|
||||
registerConfigDescriptions();
|
||||
|
||||
thing = ThingBuilder.create(THING_TYPE_UID, THING_UID).withChannels(List.of( //
|
||||
ChannelBuilder.create(CHANNEL_UID, CoreItemFactory.SWITCH).withLabel("Test Label")
|
||||
.withDescription("Test Description").withType(CHANNEL_TYPE_UID)
|
||||
@ -187,8 +197,56 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeCallsThingUpdated() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
public void testMissingBridgePreventsInitialization() {
|
||||
registerThingHandlerFactory(THING_WITH_BRIDGE_TYPE_UID, thing -> new BaseThingHandler(thing) {
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
});
|
||||
|
||||
Thing thing = ThingBuilder.create(THING_WITH_BRIDGE_TYPE_UID, THING_UID).build();
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertEquals(ThingStatus.UNINITIALIZED, thing.getStatus());
|
||||
assertEquals(ThingStatusDetail.HANDLER_CONFIGURATION_PENDING, thing.getStatusInfo().getStatusDetail(),
|
||||
thing.getStatusInfo().toString());
|
||||
});
|
||||
|
||||
managedThingProvider.remove(thing.getUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingBridgeAllowsInitialization() {
|
||||
registerThingHandlerFactory(THING_WITH_BRIDGE_TYPE_UID, thing -> new BaseThingHandler(thing) {
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
});
|
||||
|
||||
Thing thing = ThingBuilder.create(THING_WITH_BRIDGE_TYPE_UID, THING_UID).withBridge(BRIDGE_UID).build();
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
waitForAssert(() -> {
|
||||
assertEquals(ThingStatus.UNINITIALIZED, thing.getStatus());
|
||||
assertEquals(ThingStatusDetail.BRIDGE_UNINITIALIZED, thing.getStatusInfo().getStatusDetail());
|
||||
});
|
||||
|
||||
managedThingProvider.remove(thing.getUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeCallsThingUpdated() {
|
||||
AtomicReference<ThingHandlerCallback> thc = new AtomicReference<>();
|
||||
AtomicReference<Boolean> initializeRunning = new AtomicReference<>(false);
|
||||
registerThingHandlerFactory(THING_TYPE_UID, thing -> {
|
||||
@ -325,9 +383,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeOnlyIfInitializable() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
registerChannelTypeProvider();
|
||||
public void testInitializeOnlyIfInitializable() {
|
||||
registerThingHandlerFactory(THING_TYPE_UID, thing -> new BaseThingHandler(thing) {
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
@ -563,9 +619,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetEnabledWithHandler() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
|
||||
public void testSetEnabledWithHandler() {
|
||||
AtomicReference<ThingHandlerCallback> thingHandlerCallback = new AtomicReference<>();
|
||||
AtomicReference<Boolean> initializeInvoked = new AtomicReference<>(false);
|
||||
AtomicReference<Boolean> disposeInvoked = new AtomicReference<>(false);
|
||||
@ -640,8 +694,6 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
|
||||
@Test
|
||||
public void testSetEnabledWithoutHandlerFactory() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
|
||||
ThingStatusInfo thingStatusInfo = ThingStatusInfoBuilder
|
||||
.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build();
|
||||
thing.setStatusInfo(thingStatusInfo);
|
||||
@ -732,9 +784,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisposeNotInvokedOnAlreadyDisabledThing() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
|
||||
public void testDisposeNotInvokedOnAlreadyDisabledThing() {
|
||||
AtomicReference<ThingHandlerCallback> thingHandlerCallback = new AtomicReference<>();
|
||||
AtomicReference<Boolean> initializeInvoked = new AtomicReference<>(false);
|
||||
AtomicReference<Boolean> disposeInvoked = new AtomicReference<>(false);
|
||||
@ -809,9 +859,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateThing() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
|
||||
public void testUpdateThing() {
|
||||
AtomicReference<ThingHandlerCallback> thingHandlerCallback = new AtomicReference<>();
|
||||
AtomicReference<Boolean> initializeInvoked = new AtomicReference<>(false);
|
||||
AtomicReference<Boolean> disposeInvoked = new AtomicReference<>(false);
|
||||
@ -897,9 +945,7 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStorageEntryRetainedOnThingRemoval() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
|
||||
public void testStorageEntryRetainedOnThingRemoval() {
|
||||
AtomicReference<ThingHandlerCallback> thingHandlerCallback = new AtomicReference<>();
|
||||
AtomicReference<Boolean> initializeInvoked = new AtomicReference<>(false);
|
||||
AtomicReference<Boolean> disposeInvoked = new AtomicReference<>(false);
|
||||
@ -1002,28 +1048,37 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
registerService(mockThingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
}
|
||||
|
||||
private void registerThingTypeProvider() throws Exception {
|
||||
private void registerThingTypeProvider() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(THING_TYPE_UID, "label")
|
||||
.withConfigDescriptionURI(configDescriptionThing)
|
||||
.withChannelDefinitions(List.of(new ChannelDefinitionBuilder(CHANNEL_ID, CHANNEL_TYPE_UID).build()))
|
||||
.build();
|
||||
ThingType thingTypeWithBridge = ThingTypeBuilder.instance(THING_WITH_BRIDGE_TYPE_UID, "label")
|
||||
.withConfigDescriptionURI(configDescriptionThing)
|
||||
.withSupportedBridgeTypeUIDs(List.of(BRIDGE_TYPE_UID.getId()))
|
||||
.withChannelDefinitions(List.of(new ChannelDefinitionBuilder(CHANNEL_ID, CHANNEL_TYPE_UID).build()))
|
||||
.build();
|
||||
ThingType bridgeType = ThingTypeBuilder.instance(BRIDGE_TYPE_UID, "label").buildBridge();
|
||||
|
||||
ThingTypeProvider mockThingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(mockThingTypeProvider.getThingType(eq(THING_TYPE_UID), any())).thenReturn(thingType);
|
||||
when(mockThingTypeProvider.getThingType(eq(THING_WITH_BRIDGE_TYPE_UID), any())).thenReturn(thingTypeWithBridge);
|
||||
when(mockThingTypeProvider.getThingType(eq(BRIDGE_TYPE_UID), any())).thenReturn(bridgeType);
|
||||
|
||||
registerService(mockThingTypeProvider);
|
||||
}
|
||||
|
||||
private void registerChannelTypeProvider() throws Exception {
|
||||
private void registerChannelTypeProvider() {
|
||||
ChannelType channelType = ChannelTypeBuilder.state(CHANNEL_TYPE_UID, "Test Label", CoreItemFactory.SWITCH)
|
||||
.withDescription("Test Description").withCategory("Test Category").withTag("Test Tag")
|
||||
.withConfigDescriptionURI(new URI("test:channel")).build();
|
||||
.withConfigDescriptionURI(URI.create("test:channel")).build();
|
||||
|
||||
ChannelTypeProvider mockChannelTypeProvider = mock(ChannelTypeProvider.class);
|
||||
when(mockChannelTypeProvider.getChannelType(eq(CHANNEL_TYPE_UID), any())).thenReturn(channelType);
|
||||
registerService(mockChannelTypeProvider);
|
||||
}
|
||||
|
||||
private void registerChannelGroupTypeProvider() throws Exception {
|
||||
private void registerChannelGroupTypeProvider() {
|
||||
ChannelGroupType channelGroupType = ChannelGroupTypeBuilder.instance(CHANNEL_GROUP_TYPE_UID, "Test Group Label")
|
||||
.withDescription("Test Group Description").withCategory("Test Group Category")
|
||||
.withChannelDefinitions(List.of(new ChannelDefinitionBuilder(CHANNEL_ID, CHANNEL_TYPE_UID).build(),
|
||||
@ -1064,9 +1119,6 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
};
|
||||
|
||||
private AtomicReference<ThingHandlerCallback> initializeThingHandlerCallback() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
registerChannelTypeProvider();
|
||||
registerChannelGroupTypeProvider();
|
||||
AtomicReference<ThingHandlerCallback> thc = new AtomicReference<>();
|
||||
ThingHandlerFactory thingHandlerFactory = new BaseThingHandlerFactory() {
|
||||
@Override
|
||||
@ -1093,4 +1145,15 @@ public class ThingManagerOSGiJavaTest extends JavaOSGiTest {
|
||||
});
|
||||
return thc;
|
||||
}
|
||||
|
||||
private void registerConfigDescriptions() {
|
||||
ConfigDescriptionProvider configDescriptionProvider = mock(ConfigDescriptionProvider.class);
|
||||
|
||||
when(configDescriptionProvider.getConfigDescription(eq(configDescriptionThing), nullable(Locale.class)))
|
||||
.thenReturn(ConfigDescriptionBuilder.create(configDescriptionThing).build());
|
||||
when(configDescriptionProvider.getConfigDescription(eq(configDescriptionChannel), nullable(Locale.class)))
|
||||
.thenReturn(ConfigDescriptionBuilder.create(configDescriptionChannel).build());
|
||||
|
||||
registerService(configDescriptionProvider);
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,8 @@ import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyMarkerUtils;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.service.StartLevelService;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -100,9 +100,6 @@ import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.framework.InvalidSyntaxException;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
|
||||
@ -169,27 +166,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
readyService = getService(ReadyService.class);
|
||||
assertNotNull(readyService);
|
||||
|
||||
waitForAssert(() -> {
|
||||
try {
|
||||
assertThat(
|
||||
bundleContext
|
||||
.getServiceReferences(ReadyMarker.class,
|
||||
"(" + ThingManagerImpl.XML_THING_TYPE + "="
|
||||
+ bundleContext.getBundle().getSymbolicName() + ")"),
|
||||
is(notNullValue()));
|
||||
} catch (InvalidSyntaxException e) {
|
||||
fail("Failed to get service reference: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
Bundle bundle = mock(Bundle.class);
|
||||
when(bundle.getSymbolicName()).thenReturn("org.openhab.core.thing");
|
||||
|
||||
BundleResolver bundleResolver = mock(BundleResolver.class);
|
||||
when(bundleResolver.resolveBundle(any())).thenReturn(bundle);
|
||||
|
||||
ThingManagerImpl thingManager = (ThingManagerImpl) getService(ThingTypeMigrationService.class);
|
||||
thingManager.setBundleResolver(bundleResolver);
|
||||
assertNotNull(thingManager);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@ -199,6 +177,33 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
when(componentContext.getProperties()).thenReturn(new Hashtable<>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thingManagerHonorsMissingPrerequisite() {
|
||||
// set ready marker, otherwise check job is not running
|
||||
ThingManagerImpl thingManager = (ThingManagerImpl) getService(ThingManager.class);
|
||||
thingManager.onReadyMarkerAdded(new ReadyMarker(StartLevelService.STARTLEVEL_MARKER_TYPE,
|
||||
Integer.toString(StartLevelService.STARTLEVEL_MODEL)));
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
when(thingHandler.getThing()).thenReturn(thing);
|
||||
|
||||
ThingHandlerFactory thingHandlerFactory = mock(ThingHandlerFactory.class);
|
||||
when(thingHandlerFactory.supportsThingType(any(ThingTypeUID.class))).thenReturn(true);
|
||||
when(thingHandlerFactory.registerHandler(any(Thing.class))).thenReturn(thingHandler);
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
assertThat(thing.getStatus(), is(ThingStatus.UNINITIALIZED));
|
||||
assertThat(thing.getStatusInfo().getStatusDetail(), is(ThingStatusDetail.NOT_YET_READY));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.INITIALIZING)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
public void thingManagerChangesTheThingType() {
|
||||
@ -231,6 +236,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerChangesTheThingTypeCorrectlyEvenIfInitializeTakesLongAndCalledFromThere() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
ThingTypeUID newThingTypeUID = new ThingTypeUID("binding:type2");
|
||||
|
||||
@ -317,6 +323,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerWaitsWithThingUpdatedUntilInitializeReturned() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
Thing thing2 = ThingBuilder.create(THING_TYPE_UID, THING_UID)
|
||||
.withChannels(List.of(ChannelBuilder.create(CHANNEL_UID, CoreItemFactory.SWITCH).build())).build();
|
||||
@ -379,6 +386,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
when(thingHandlerFactory.registerHandler(any(Thing.class))).thenReturn(thingHandler);
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -395,6 +404,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
when(thingHandlerFactory.registerHandler(any(Thing.class))).thenReturn(thingHandler);
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
managedThingProvider.remove(thing.getUID());
|
||||
@ -442,6 +453,9 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build();
|
||||
assertThat(thing.getStatusInfo(), is(uninitializedNone));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
// add thing - provokes handler registration & initialization
|
||||
managedThingProvider.add(thing);
|
||||
waitForAssert(() -> verify(thingHandlerFactory, times(1)).registerHandler(thing));
|
||||
@ -521,6 +535,9 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build();
|
||||
assertThat(testThing.getStatusInfo(), is(uninitializedNone));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(testThing);
|
||||
final ThingStatusInfo uninitializedError = ThingStatusInfoBuilder
|
||||
.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_INITIALIZING_ERROR)
|
||||
@ -684,6 +701,9 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
ThingRegistry thingRegistry = getService(ThingRegistry.class);
|
||||
assertThat(thingRegistry, not(nullValue()));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
// add thing - no thing initialization, because bridge is not available
|
||||
thingRegistry.add(thing);
|
||||
waitForAssert(() -> assertThat(thingState.initCalled, is(false)));
|
||||
@ -767,6 +787,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerDoesNotDelegateUpdateEventsToItsSource() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
class ThingHandlerState {
|
||||
boolean handleCommandWasCalled;
|
||||
@ -829,6 +850,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerHandlesStateUpdatesCorrectly() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
class ThingHandlerState {
|
||||
boolean thingUpdatedWasCalled;
|
||||
@ -931,6 +953,9 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
ThingHandlerCallback callback;
|
||||
}
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
final ThingHandlerState state = new ThingHandlerState();
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
@ -1000,6 +1025,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
final ThingHandlerState state = new ThingHandlerState();
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -1048,6 +1075,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerHandlesThingStatusUpdatesUninitializedAndInitializingCorrectly() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
when(thingHandler.getThing()).thenReturn(thing);
|
||||
@ -1078,6 +1106,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerHandlesThingStatusUpdatesRemovingAndInitializingCorrectly() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
when(thingHandler.getThing()).thenReturn(thing);
|
||||
@ -1111,6 +1140,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
when(thingHandlerFactory.registerHandler(any(Thing.class))).thenThrow(new RuntimeException(exceptionMessage));
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
@ -1124,6 +1155,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@SuppressWarnings({ "null", "unchecked" })
|
||||
public void thingManagerHandlesThingUpdatesCorrectly() {
|
||||
String itemName = "name";
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, CHANNEL_UID));
|
||||
@ -1196,6 +1229,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerPostsThingStatusEventsIfTheStatusOfAThingIsUpdated() {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
class ThingHandlerState {
|
||||
@Nullable
|
||||
@ -1283,6 +1317,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void thingManagerPostsThingStatusChangedEventsIfTheStatusOfAThingIsChanged() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
class ThingHandlerState {
|
||||
@Nullable
|
||||
@ -1355,6 +1390,7 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
@SuppressWarnings("null")
|
||||
public void thingManagerPostsLocalizedThingStatusInfoAndThingStatusInfoChangedEvents() throws Exception {
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
class ThingHandlerState {
|
||||
@Nullable
|
||||
@ -1573,64 +1609,6 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
public void thingManagerWaitsWithInitializeUntilBundleProcessingIsFinished() throws Exception {
|
||||
Thing thing = ThingBuilder.create(THING_TYPE_UID, THING_UID).build();
|
||||
|
||||
class ThingHandlerState {
|
||||
@SuppressWarnings("unused")
|
||||
@Nullable
|
||||
ThingHandlerCallback callback;
|
||||
}
|
||||
final ThingHandlerState state = new ThingHandlerState();
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
doAnswer(new Answer<Void>() {
|
||||
@Override
|
||||
public @Nullable Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
state.callback = (ThingHandlerCallback) invocation.getArgument(0);
|
||||
return null;
|
||||
}
|
||||
}).when(thingHandler).setCallback(any(ThingHandlerCallback.class));
|
||||
when(thingHandler.getThing()).thenReturn(thing);
|
||||
|
||||
ThingHandlerFactory thingHandlerFactory = mock(ThingHandlerFactory.class);
|
||||
when(thingHandlerFactory.supportsThingType(any(ThingTypeUID.class))).thenReturn(true);
|
||||
when(thingHandlerFactory.registerHandler(any(Thing.class))).thenReturn(thingHandler);
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
|
||||
final ReadyMarker marker = new ReadyMarker(ThingManagerImpl.XML_THING_TYPE,
|
||||
ReadyMarkerUtils.getIdentifier(FrameworkUtil.getBundle(this.getClass())));
|
||||
waitForAssert(() -> {
|
||||
// wait for the XML processing to be finished, then remove the ready marker again
|
||||
assertThat(readyService.isReady(marker), is(true));
|
||||
readyService.unmarkReady(marker);
|
||||
});
|
||||
|
||||
ThingStatusInfo uninitializedNone = ThingStatusInfoBuilder
|
||||
.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build();
|
||||
assertThat(thing.getStatusInfo(), is(uninitializedNone));
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
// just wait a little to make sure really nothing happens
|
||||
Thread.sleep(1000);
|
||||
verify(thingHandler, never()).initialize();
|
||||
assertThat(thing.getStatusInfo(), is(uninitializedNone));
|
||||
|
||||
readyService.markReady(marker);
|
||||
|
||||
// ThingHandler.initialize() called, thing status is INITIALIZING.NONE
|
||||
ThingStatusInfo initializingNone = ThingStatusInfoBuilder
|
||||
.create(ThingStatus.INITIALIZING, ThingStatusDetail.NONE).build();
|
||||
waitForAssert(() -> {
|
||||
verify(thingHandler, times(1)).initialize();
|
||||
assertThat(thing.getStatusInfo(), is(initializingNone));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("null")
|
||||
public void thingManagerCallsBridgeStatusChangedOnThingHandlerCorrectly() {
|
||||
@ -1719,6 +1697,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
}).when(thingHandlerFactory).registerHandler(any(Thing.class));
|
||||
|
||||
registerService(thingHandlerFactory);
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(bridge);
|
||||
managedThingProvider.add(thing);
|
||||
@ -1859,6 +1839,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
}).when(thingHandlerFactory).registerHandler(any(Thing.class));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
registerService(thingHandlerFactory);
|
||||
|
||||
managedThingProvider.add(bridge);
|
||||
@ -1985,6 +1967,8 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
}).when(thingHandlerFactory).registerHandler(any(Thing.class));
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
registerService(thingHandlerFactory);
|
||||
|
||||
managedThingProvider.add(bridge);
|
||||
@ -2016,6 +2000,9 @@ public class ThingManagerOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
final ThingHandlerState state = new ThingHandlerState();
|
||||
|
||||
registerThingTypeProvider();
|
||||
registerConfigDescriptionProvider(false);
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
ThingHandler thingHandler = mock(ThingHandler.class);
|
||||
|
@ -16,8 +16,13 @@ import static java.util.Map.entry;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@ -44,10 +49,14 @@ import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.events.ThingAddedEvent;
|
||||
import org.openhab.core.thing.events.ThingRemovedEvent;
|
||||
import org.openhab.core.thing.events.ThingUpdatedEvent;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
|
||||
@ -76,6 +85,7 @@ public class ThingRegistryOSGiTest extends JavaOSGiTest {
|
||||
public void setUp() {
|
||||
registerVolatileStorageService();
|
||||
managedThingProvider = getService(ManagedThingProvider.class);
|
||||
registerThingTypeProvider();
|
||||
unregisterCurrentThingHandlerFactory();
|
||||
}
|
||||
|
||||
@ -246,4 +256,16 @@ public class ThingRegistryOSGiTest extends JavaOSGiTest {
|
||||
thingHandlerFactoryServiceReg = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerThingTypeProvider() {
|
||||
ThingType thingType = ThingTypeBuilder.instance(THING_TYPE_UID, "label").build();
|
||||
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(thingTypeProvider.getThingType(any(ThingTypeUID.class), nullable(Locale.class))).thenReturn(thingType);
|
||||
registerService(thingTypeProvider);
|
||||
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
when(thingTypeRegistry.getThingType(any(ThingTypeUID.class))).thenReturn(thingType);
|
||||
registerService(thingTypeRegistry);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,384 @@
|
||||
/**
|
||||
* 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.thing.internal.update;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.core.thing.internal.ThingManagerImpl.PROPERTY_THING_TYPE_VERSION;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.test.SyntheticBundleInstaller;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ManagedThingProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingTypeProvider;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeProvider;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.ThingType;
|
||||
import org.openhab.core.thing.type.ThingTypeBuilder;
|
||||
import org.openhab.core.thing.type.ThingTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.BundleResolver;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tests for {@link ThingUpdateInstructionReader} and {@link ThingUpdateInstruction} implementations.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class ThingUpdateOSGiTest extends JavaOSGiTest {
|
||||
|
||||
private static final String TEST_BUNDLE_NAME = "thingUpdateTest.bundle";
|
||||
private static final String BINDING_ID = "testBinding";
|
||||
private static final ThingTypeUID ADD_CHANNEL_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "testThingTypeAdd");
|
||||
private static final ThingTypeUID UPDATE_CHANNEL_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
|
||||
"testThingTypeUpdate");
|
||||
private static final ThingTypeUID REMOVE_CHANNEL_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
|
||||
"testThingTypeRemove");
|
||||
private static final ThingTypeUID MULTIPLE_CHANNEL_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
|
||||
"testThingTypeMultiple");
|
||||
|
||||
private static final String[] ADDED_TAGS = { "Tag1", "Tag2" };
|
||||
|
||||
private static final String THING_ID = "thing";
|
||||
|
||||
private @NonNullByDefault({}) Bundle testBundle;
|
||||
private @NonNullByDefault({}) BundleResolver bundleResolver;
|
||||
private @NonNullByDefault({}) ReadyService readyService;
|
||||
private @NonNullByDefault({}) ThingRegistry thingRegistry;
|
||||
private @NonNullByDefault({}) ManagedThingProvider managedThingProvider;
|
||||
private @NonNullByDefault({}) TestThingHandlerFactory thingHandlerFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception {
|
||||
Hashtable<String, Object> properties = new Hashtable<>();
|
||||
properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
|
||||
bundleResolver = new BundleResolverImpl();
|
||||
registerService(bundleResolver, BundleResolver.class.getName(), properties);
|
||||
|
||||
registerVolatileStorageService();
|
||||
|
||||
testBundle = SyntheticBundleInstaller.install(bundleContext, TEST_BUNDLE_NAME, "*.xml");
|
||||
assertThat(testBundle, is(notNullValue()));
|
||||
|
||||
readyService = getService(ReadyService.class);
|
||||
assertThat(readyService, is(notNullValue()));
|
||||
|
||||
thingRegistry = getService(ThingRegistry.class);
|
||||
assertThat(thingRegistry, is(notNullValue()));
|
||||
|
||||
managedThingProvider = getService(ManagedThingProvider.class);
|
||||
assertThat(managedThingProvider, is(notNullValue()));
|
||||
|
||||
thingHandlerFactory = new TestThingHandlerFactory();
|
||||
registerService(thingHandlerFactory, ThingHandlerFactory.class.getName());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void afterEach() throws Exception {
|
||||
testBundle.uninstall();
|
||||
managedThingProvider.getAll().forEach(t -> managedThingProvider.remove(t.getUID()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleChannelAddition() {
|
||||
registerThingType(ADD_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, "testChannelTypeId");
|
||||
registerChannelTypes(channelTypeUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(ADD_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
Thing thing = ThingBuilder.create(ADD_CHANNEL_THING_TYPE_UID, thingUID).build();
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
Thing updatedThing = assertThing(thing, 1);
|
||||
|
||||
assertThat(updatedThing.getChannels(), hasSize(3));
|
||||
|
||||
Channel channel1 = updatedThing.getChannel("testChannel1");
|
||||
assertChannel(channel1, channelTypeUID, null, null);
|
||||
|
||||
Channel channel2 = updatedThing.getChannel("testChannel2");
|
||||
assertChannel(channel2, channelTypeUID, "Test Label", null);
|
||||
assertThat(channel2.getDefaultTags(), containsInAnyOrder(ADDED_TAGS));
|
||||
|
||||
Channel channel3 = updatedThing.getChannel("testChannel3");
|
||||
assertChannel(channel3, channelTypeUID, "Test Label", "Test Description");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleChannelUpdate() {
|
||||
registerThingType(UPDATE_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeOldUID = new ChannelTypeUID(BINDING_ID, "testChannelOldTypeId");
|
||||
ChannelTypeUID channelTypeNewUID = new ChannelTypeUID(BINDING_ID, "testChannelNewTypeId");
|
||||
registerChannelTypes(channelTypeOldUID, channelTypeNewUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(UPDATE_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
ChannelUID channelUID1 = new ChannelUID(thingUID, "testChannel1");
|
||||
ChannelUID channelUID2 = new ChannelUID(thingUID, "testChannel2");
|
||||
Configuration channelConfig = new Configuration(Map.of("foo", "bar"));
|
||||
|
||||
Channel origChannel1 = ChannelBuilder.create(channelUID1).withType(channelTypeOldUID)
|
||||
.withConfiguration(channelConfig).build();
|
||||
Channel origChannel2 = ChannelBuilder.create(channelUID2).withType(channelTypeOldUID)
|
||||
.withConfiguration(channelConfig).build();
|
||||
|
||||
Thing thing = ThingBuilder.create(UPDATE_CHANNEL_THING_TYPE_UID, thingUID)
|
||||
.withChannels(origChannel1, origChannel2).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
Thing updatedThing = assertThing(thing, 1);
|
||||
assertThat(updatedThing.getChannels(), hasSize(2));
|
||||
|
||||
Channel channel1 = updatedThing.getChannel(channelUID1);
|
||||
assertChannel(channel1, channelTypeNewUID, "New Test Label", null);
|
||||
assertThat(channel1.getConfiguration(), is(channelConfig));
|
||||
|
||||
Channel channel2 = updatedThing.getChannel(channelUID2);
|
||||
assertChannel(channel2, channelTypeNewUID, null, null);
|
||||
assertThat(channel2.getConfiguration().getProperties(), is(anEmptyMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleChannelRemoval() {
|
||||
registerThingType(REMOVE_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, "testChannelTypeId");
|
||||
registerChannelTypes(channelTypeUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(REMOVE_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
ChannelUID channelUID = new ChannelUID(thingUID, "testChannel");
|
||||
|
||||
Thing thing = ThingBuilder.create(REMOVE_CHANNEL_THING_TYPE_UID, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID).withType(channelTypeUID).build()).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
Thing updatedThing = assertThing(thing, 1);
|
||||
assertThat(updatedThing.getChannels(), hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleChannelUpdates() {
|
||||
registerThingType(MULTIPLE_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeOldUID = new ChannelTypeUID(BINDING_ID, "testChannelOldTypeId");
|
||||
ChannelTypeUID channelTypeNewUID = new ChannelTypeUID(BINDING_ID, "testChannelNewTypeId");
|
||||
registerChannelTypes(channelTypeOldUID, channelTypeNewUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(MULTIPLE_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
ChannelUID channelUID0 = new ChannelUID(thingUID, "testChannel0");
|
||||
ChannelUID channelUID1 = new ChannelUID(thingUID, "testChannel1");
|
||||
|
||||
Thing thing = ThingBuilder.create(MULTIPLE_CHANNEL_THING_TYPE_UID, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID0).withType(channelTypeOldUID).build())
|
||||
.withChannel(ChannelBuilder.create(channelUID1).withType(channelTypeOldUID).build()).build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
Thing updatedThing = assertThing(thing, 3);
|
||||
assertThat(updatedThing.getChannels(), hasSize(2));
|
||||
|
||||
Channel channel1 = updatedThing.getChannel("testChannel1");
|
||||
assertChannel(channel1, channelTypeNewUID, "Test Label", null);
|
||||
|
||||
Channel channel2 = updatedThing.getChannel("testChannel2");
|
||||
assertChannel(channel2, channelTypeOldUID, "TestLabel", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyMatchingInstructionsUpdate() {
|
||||
registerThingType(MULTIPLE_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeOldUID = new ChannelTypeUID(BINDING_ID, "testChannelOldTypeId");
|
||||
registerChannelTypes(channelTypeOldUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(MULTIPLE_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
ChannelUID channelUID0 = new ChannelUID(thingUID, "testChannel0");
|
||||
ChannelUID channelUID1 = new ChannelUID(thingUID, "testChannel1");
|
||||
|
||||
Thing thing = ThingBuilder.create(MULTIPLE_CHANNEL_THING_TYPE_UID, thingUID)
|
||||
.withChannel(ChannelBuilder.create(channelUID0).withType(channelTypeOldUID).build())
|
||||
.withChannel(ChannelBuilder.create(channelUID1).withType(channelTypeOldUID).build())
|
||||
.withProperty(PROPERTY_THING_TYPE_VERSION, "2").build();
|
||||
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
Thing updatedThing = assertThing(thing, 3);
|
||||
assertThat(updatedThing.getChannels(), hasSize(1));
|
||||
|
||||
Channel channel1 = updatedThing.getChannel("testChannel1");
|
||||
assertChannel(channel1, channelTypeOldUID, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotModifiedIfHigherVersion() {
|
||||
registerThingType(ADD_CHANNEL_THING_TYPE_UID);
|
||||
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, "testChannelTypeId");
|
||||
registerChannelTypes(channelTypeUID);
|
||||
|
||||
ThingUID thingUID = new ThingUID(ADD_CHANNEL_THING_TYPE_UID, THING_ID);
|
||||
Thing thing = ThingBuilder.create(ADD_CHANNEL_THING_TYPE_UID, thingUID)
|
||||
.withProperty(PROPERTY_THING_TYPE_VERSION, "1").build();
|
||||
managedThingProvider.add(thing);
|
||||
|
||||
waitForAssert(() -> assertThat(thing.getStatus(), is(ThingStatus.ONLINE)));
|
||||
assertThat(thingRegistry.get(thingUID), is(sameInstance(thing)));
|
||||
assertThat(thing.getChannels(), is(emptyCollectionOf(Channel.class)));
|
||||
}
|
||||
|
||||
private Thing assertThing(Thing oldThing, int expectedNewThingTypeVersion) {
|
||||
ThingUID thingUID = oldThing.getUID();
|
||||
|
||||
waitForAssert(() -> {
|
||||
@Nullable
|
||||
Thing updatedThing = thingRegistry.get(thingUID);
|
||||
assertThat(updatedThing, is(not(sameInstance(oldThing))));
|
||||
});
|
||||
|
||||
@Nullable
|
||||
Thing updatedThing = thingRegistry.get(thingUID);
|
||||
assertThat(updatedThing.getStatus(), is(ThingStatus.ONLINE));
|
||||
|
||||
// check thing type version is upgraded
|
||||
String thingTypeVersion = updatedThing.getProperties().get(PROPERTY_THING_TYPE_VERSION);
|
||||
assertThat(thingTypeVersion, is(Integer.toString(expectedNewThingTypeVersion)));
|
||||
|
||||
return updatedThing;
|
||||
}
|
||||
|
||||
private void assertChannel(@Nullable Channel channel, ChannelTypeUID channelTypeUID, @Nullable String label,
|
||||
@Nullable String description) {
|
||||
assertThat(channel, is(notNullValue()));
|
||||
assertThat(channel.getChannelTypeUID(), is(channelTypeUID));
|
||||
if (label != null) {
|
||||
assertThat(channel.getLabel(), is(label));
|
||||
} else {
|
||||
assertThat(channel.getLabel(), is(nullValue()));
|
||||
}
|
||||
if (description != null) {
|
||||
assertThat(channel.getDescription(), is(description));
|
||||
} else {
|
||||
assertThat(channel.getDescription(), is(nullValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerThingType(ThingTypeUID thingTypeUID) {
|
||||
ThingType thingType = ThingTypeBuilder.instance(thingTypeUID, "label").build();
|
||||
|
||||
ThingTypeProvider thingTypeProvider = mock(ThingTypeProvider.class);
|
||||
when(thingTypeProvider.getThingType(eq(thingTypeUID), nullable(Locale.class))).thenReturn(thingType);
|
||||
registerService(thingTypeProvider);
|
||||
|
||||
ThingTypeRegistry thingTypeRegistry = mock(ThingTypeRegistry.class);
|
||||
when(thingTypeRegistry.getThingType(eq(thingTypeUID))).thenReturn(thingType);
|
||||
registerService(thingTypeRegistry);
|
||||
}
|
||||
|
||||
private void registerChannelTypes(ChannelTypeUID... channelTypeUIDs) {
|
||||
ChannelTypeProvider channelTypeProvider = mock(ChannelTypeProvider.class);
|
||||
ChannelTypeRegistry channelTypeRegistry = mock(ChannelTypeRegistry.class);
|
||||
|
||||
for (ChannelTypeUID channelTypeUID : channelTypeUIDs) {
|
||||
ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, "label", "Number").build();
|
||||
when(channelTypeProvider.getChannelType(eq(channelTypeUID), nullable(Locale.class)))
|
||||
.thenReturn(channelType);
|
||||
when(channelTypeRegistry.getChannelType(eq(channelTypeUID))).thenReturn(channelType);
|
||||
}
|
||||
|
||||
registerService(channelTypeProvider);
|
||||
registerService(channelTypeRegistry);
|
||||
}
|
||||
|
||||
class TestThingHandlerFactory extends BaseThingHandlerFactory {
|
||||
Logger logger = LoggerFactory.getLogger(TestThingHandlerFactory.class);
|
||||
|
||||
@Override
|
||||
public void activate(final ComponentContext ctx) {
|
||||
super.activate(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
return new BaseThingHandler(thing) {
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class BundleResolverImpl implements BundleResolver {
|
||||
@Override
|
||||
public Bundle resolveBundle(@NonNullByDefault({}) Class<?> clazz) {
|
||||
// return the test bundle if the class is TestThingHandlerFactory
|
||||
if (clazz != null && clazz.equals(TestThingHandlerFactory.class)) {
|
||||
return testBundle;
|
||||
} else {
|
||||
return FrameworkUtil.getBundle(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||
|
||||
<thing-type uid="testBinding:testThingTypeMultiple">
|
||||
<instruction-set targetVersion="1">
|
||||
<update-channel id="testChannel1">
|
||||
<type>testBinding:testChannelNewTypeId</type>
|
||||
<label>Test Label</label>
|
||||
</update-channel>
|
||||
</instruction-set>
|
||||
<instruction-set targetVersion="2">
|
||||
<add-channel id="testChannel2">
|
||||
<type>testBinding:testChannelOldTypeId</type>
|
||||
<label>TestLabel</label>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
<instruction-set targetVersion="3">
|
||||
<remove-channel id="testChannel0"/>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
|
||||
</update:update-descriptions>
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||
|
||||
<thing-type uid="testBinding:testThingTypeAdd">
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="testChannel1">
|
||||
<type>testBinding:testChannelTypeId</type>
|
||||
</add-channel>
|
||||
<add-channel id="testChannel2">
|
||||
<type>testBinding:testChannelTypeId</type>
|
||||
<label>Test Label</label>
|
||||
<tags>
|
||||
<tag>Tag1</tag>
|
||||
<tag>Tag2</tag>
|
||||
</tags>
|
||||
</add-channel>
|
||||
<add-channel id="testChannel3">
|
||||
<type>testBinding:testChannelTypeId</type>
|
||||
<label>Test Label</label>
|
||||
<description>Test Description</description>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
|
||||
<thing-type uid="testBinding:testThingTypeRemove">
|
||||
<instruction-set targetVersion="1">
|
||||
<remove-channel id="testChannel"/>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
|
||||
<thing-type uid="testBinding:testThingTypeUpdate">
|
||||
<instruction-set targetVersion="1">
|
||||
<update-channel id="testChannel1">
|
||||
<type>testBinding:testChannelNewTypeId</type>
|
||||
<label>New Test Label</label>
|
||||
</update-channel>
|
||||
<update-channel id="testChannel2" preserveConfiguration="false">
|
||||
<type>testBinding:testChannelNewTypeId</type>
|
||||
</update-channel>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
|
||||
</update:update-descriptions>
|
Loading…
Reference in New Issue
Block a user