YAML things provider: create things even if binding is not yet installed (#4753)
Makes it consistent with managed thing provider. Removes the OSGi reference between YamlThingProvider and YamlModelRepository and as consequence removes the circular reference. Signed-off-by: Laurent Garnier <lg.hc@free.fr>pull/4776/head
parent
fd171e26d7
commit
3df33758d4
|
@ -21,7 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
* The {@link YamlModelRepository} defines methods to update elements in a YAML model.
|
* The {@link YamlModelRepository} defines methods to update elements in a YAML model.
|
||||||
*
|
*
|
||||||
* @author Jan N. Klug - Initial contribution
|
* @author Jan N. Klug - Initial contribution
|
||||||
* @author Laurent Garnier - Added methods refreshModelElements and generateSyntaxFromElements
|
* @author Laurent Garnier - Added method generateSyntaxFromElements
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface YamlModelRepository {
|
public interface YamlModelRepository {
|
||||||
|
@ -31,14 +31,6 @@ public interface YamlModelRepository {
|
||||||
|
|
||||||
void updateElementInModel(String modelName, YamlElement element);
|
void updateElementInModel(String modelName, YamlElement element);
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers the refresh of a certain type of elements in a given model.
|
|
||||||
*
|
|
||||||
* @param modelName the model name
|
|
||||||
* @param elementName the type of elements to refresh
|
|
||||||
*/
|
|
||||||
void refreshModelElements(String modelName, String elementName);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the YAML syntax from a provided list of elements.
|
* Generate the YAML syntax from a provided list of elements.
|
||||||
*
|
*
|
||||||
|
|
|
@ -77,7 +77,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
|
||||||
* @author Jan N. Klug - Refactored for multiple types per file and add modifying possibility
|
* @author Jan N. Klug - Refactored for multiple types per file and add modifying possibility
|
||||||
* @author Laurent Garnier - Introduce version 2 using map instead of table
|
* @author Laurent Garnier - Introduce version 2 using map instead of table
|
||||||
* @author Laurent Garnier - Added basic version management
|
* @author Laurent Garnier - Added basic version management
|
||||||
* @author Laurent Garnier - Added methods refreshModelElements and generateSyntaxFromElements + new parameters
|
* @author Laurent Garnier - Added method generateSyntaxFromElements + new parameters
|
||||||
* for method isValid
|
* for method isValid
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@ -564,35 +564,6 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
|
||||||
writeModel(modelName);
|
writeModel(modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
||||||
public void refreshModelElements(String modelName, String elementName) {
|
|
||||||
logger.info("Refreshing {} from YAML model {}", elementName, modelName);
|
|
||||||
YamlModelWrapper model = modelCache.get(modelName);
|
|
||||||
if (model == null) {
|
|
||||||
logger.warn("Failed to refresh model {} because it is not known.", modelName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<JsonNode> modelNodes = model.getNodesV1().get(elementName);
|
|
||||||
JsonNode modelMapNode = model.getNodes().get(elementName);
|
|
||||||
if (modelNodes == null && modelMapNode == null) {
|
|
||||||
logger.warn("Failed to refresh model {} because type {} is not known in the model.", modelName,
|
|
||||||
elementName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getElementListeners(elementName, model.getVersion()).forEach(listener -> {
|
|
||||||
Class<? extends YamlElement> elementClass = listener.getElementClass();
|
|
||||||
|
|
||||||
List elements = parseJsonNodes(modelNodes != null ? modelNodes : List.of(), modelMapNode, elementClass,
|
|
||||||
null, null);
|
|
||||||
if (!elements.isEmpty()) {
|
|
||||||
listener.updatedModel(modelName, elements);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeModel(String modelName) {
|
private void writeModel(String modelName) {
|
||||||
YamlModelWrapper model = modelCache.get(modelName);
|
YamlModelWrapper model = modelCache.get(modelName);
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
|
|
|
@ -32,9 +32,7 @@ import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||||
import org.openhab.core.config.core.ConfigUtil;
|
import org.openhab.core.config.core.ConfigUtil;
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.i18n.LocaleProvider;
|
import org.openhab.core.i18n.LocaleProvider;
|
||||||
import org.openhab.core.model.yaml.YamlElementName;
|
|
||||||
import org.openhab.core.model.yaml.YamlModelListener;
|
import org.openhab.core.model.yaml.YamlModelListener;
|
||||||
import org.openhab.core.model.yaml.YamlModelRepository;
|
|
||||||
import org.openhab.core.service.ReadyMarker;
|
import org.openhab.core.service.ReadyMarker;
|
||||||
import org.openhab.core.service.ReadyService;
|
import org.openhab.core.service.ReadyService;
|
||||||
import org.openhab.core.service.StartLevelService;
|
import org.openhab.core.service.StartLevelService;
|
||||||
|
@ -84,7 +82,6 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(YamlThingProvider.class);
|
private final Logger logger = LoggerFactory.getLogger(YamlThingProvider.class);
|
||||||
|
|
||||||
private final YamlModelRepository modelRepository;
|
|
||||||
private final BundleResolver bundleResolver;
|
private final BundleResolver bundleResolver;
|
||||||
private final ThingTypeRegistry thingTypeRegistry;
|
private final ThingTypeRegistry thingTypeRegistry;
|
||||||
private final ChannelTypeRegistry channelTypeRegistry;
|
private final ChannelTypeRegistry channelTypeRegistry;
|
||||||
|
@ -104,32 +101,8 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
logger.debug("Starting lazy retry thread");
|
logger.debug("Starting lazy retry thread");
|
||||||
while (!queue.isEmpty()) {
|
while (!queue.isEmpty()) {
|
||||||
for (QueueContent qc : queue) {
|
for (QueueContent qc : queue) {
|
||||||
logger.trace("Retry creating thing {}", qc.thingUID);
|
if (retryCreateThing(qc.thingHandlerFactory, qc.thingTypeUID, qc.configuration, qc.thingUID,
|
||||||
Thing newThing = qc.thingHandlerFactory.createThing(qc.thingTypeUID, qc.configuration, qc.thingUID,
|
qc.bridgeUID)) {
|
||||||
qc.bridgeUID);
|
|
||||||
if (newThing != null) {
|
|
||||||
logger.debug("Successfully loaded thing \'{}\' during retry", qc.thingUID);
|
|
||||||
Thing oldThing = null;
|
|
||||||
for (Map.Entry<String, Collection<Thing>> entry : thingsMap.entrySet()) {
|
|
||||||
oldThing = entry.getValue().stream().filter(t -> t.getUID().equals(newThing.getUID()))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (oldThing != null) {
|
|
||||||
mergeThing(newThing, oldThing);
|
|
||||||
Collection<Thing> thingsForModel = Objects
|
|
||||||
.requireNonNull(thingsMap.get(entry.getKey()));
|
|
||||||
thingsForModel.remove(oldThing);
|
|
||||||
thingsForModel.add(newThing);
|
|
||||||
logger.debug("Refreshing thing \'{}\' after successful retry", newThing.getUID());
|
|
||||||
if (!ThingHelper.equals(oldThing, newThing)) {
|
|
||||||
notifyListenersAboutUpdatedElement(oldThing, newThing);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldThing == null) {
|
|
||||||
logger.debug("Refreshing thing \'{}\' after retry failed because thing is not found",
|
|
||||||
newThing.getUID());
|
|
||||||
}
|
|
||||||
queue.remove(qc);
|
queue.remove(qc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,12 +126,11 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public YamlThingProvider(final @Reference YamlModelRepository modelRepository,
|
public YamlThingProvider(final @Reference BundleResolver bundleResolver,
|
||||||
final @Reference BundleResolver bundleResolver, final @Reference ThingTypeRegistry thingTypeRegistry,
|
final @Reference ThingTypeRegistry thingTypeRegistry,
|
||||||
final @Reference ChannelTypeRegistry channelTypeRegistry,
|
final @Reference ChannelTypeRegistry channelTypeRegistry,
|
||||||
final @Reference ConfigDescriptionRegistry configDescriptionRegistry,
|
final @Reference ConfigDescriptionRegistry configDescriptionRegistry,
|
||||||
final @Reference LocaleProvider localeProvider) {
|
final @Reference LocaleProvider localeProvider) {
|
||||||
this.modelRepository = modelRepository;
|
|
||||||
this.bundleResolver = bundleResolver;
|
this.bundleResolver = bundleResolver;
|
||||||
this.thingTypeRegistry = thingTypeRegistry;
|
this.thingTypeRegistry = thingTypeRegistry;
|
||||||
this.channelTypeRegistry = channelTypeRegistry;
|
this.channelTypeRegistry = channelTypeRegistry;
|
||||||
|
@ -242,6 +214,7 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
|
|
||||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||||
protected void addThingHandlerFactory(final ThingHandlerFactory thingHandlerFactory) {
|
protected void addThingHandlerFactory(final ThingHandlerFactory thingHandlerFactory) {
|
||||||
|
logger.debug("addThingHandlerFactory {}", thingHandlerFactory.getClass().getSimpleName());
|
||||||
thingHandlerFactories.add(thingHandlerFactory);
|
thingHandlerFactories.add(thingHandlerFactory);
|
||||||
thingHandlerFactoryAdded(thingHandlerFactory);
|
thingHandlerFactoryAdded(thingHandlerFactory);
|
||||||
}
|
}
|
||||||
|
@ -278,18 +251,75 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
loadedXmlThingTypes.remove(readyMarker.getIdentifier());
|
loadedXmlThingTypes.remove(readyMarker.getIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void thingHandlerFactoryAdded(ThingHandlerFactory thingHandlerFactory) {
|
private void thingHandlerFactoryAdded(ThingHandlerFactory handlerFactory) {
|
||||||
String bundleName = getBundleName(thingHandlerFactory);
|
logger.debug("thingHandlerFactoryAdded {} isThingHandlerFactoryReady={}",
|
||||||
if (bundleName != null && loadedXmlThingTypes.contains(bundleName)) {
|
handlerFactory.getClass().getSimpleName(), isThingHandlerFactoryReady(handlerFactory));
|
||||||
logger.debug("Refreshing models due to new thing handler factory {}",
|
if (isThingHandlerFactoryReady(handlerFactory)) {
|
||||||
thingHandlerFactory.getClass().getSimpleName());
|
if (!thingsMap.isEmpty()) {
|
||||||
thingsMap.keySet().forEach(modelName -> {
|
logger.debug("Refreshing models due to new thing handler factory {}",
|
||||||
modelRepository.refreshModelElements(modelName,
|
handlerFactory.getClass().getSimpleName());
|
||||||
getElementClass().getAnnotation(YamlElementName.class).value());
|
thingsMap.keySet().forEach(modelName -> {
|
||||||
});
|
List<Thing> things = thingsMap.getOrDefault(modelName, List.of()).stream()
|
||||||
|
.filter(th -> handlerFactory.supportsThingType(th.getThingTypeUID())).toList();
|
||||||
|
if (!things.isEmpty()) {
|
||||||
|
logger.info("Refreshing YAML model {} ({} things with {})", modelName, things.size(),
|
||||||
|
handlerFactory.getClass().getSimpleName());
|
||||||
|
things.forEach(thing -> {
|
||||||
|
if (!retryCreateThing(handlerFactory, thing.getThingTypeUID(), thing.getConfiguration(),
|
||||||
|
thing.getUID(), thing.getBridgeUID())) {
|
||||||
|
// Possible cause: Asynchronous loading of the XML files
|
||||||
|
// Add the data to the queue in order to retry it later
|
||||||
|
logger.debug(
|
||||||
|
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
|
||||||
|
handlerFactory.getClass().getSimpleName(), thing.getThingTypeUID());
|
||||||
|
queueRetryThingCreation(handlerFactory, thing.getThingTypeUID(),
|
||||||
|
thing.getConfiguration(), thing.getUID(), thing.getBridgeUID());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.debug("No refresh needed from YAML model {}", modelName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.debug("No things yet loaded; no need to trigger a refresh due to new thing handler factory");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean retryCreateThing(ThingHandlerFactory handlerFactory, ThingTypeUID thingTypeUID,
|
||||||
|
Configuration configuration, ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||||
|
logger.trace("Retry creating thing {}", thingUID);
|
||||||
|
Thing newThing = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||||
|
if (newThing != null) {
|
||||||
|
logger.debug("Successfully loaded thing \'{}\' during retry", thingUID);
|
||||||
|
Thing oldThing = null;
|
||||||
|
for (Collection<Thing> modelThings : thingsMap.values()) {
|
||||||
|
oldThing = modelThings.stream().filter(t -> t.getUID().equals(newThing.getUID())).findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (oldThing != null) {
|
||||||
|
mergeThing(newThing, oldThing);
|
||||||
|
modelThings.remove(oldThing);
|
||||||
|
modelThings.add(newThing);
|
||||||
|
logger.debug("Refreshing thing \'{}\' after successful retry", newThing.getUID());
|
||||||
|
if (!ThingHelper.equals(oldThing, newThing)) {
|
||||||
|
notifyListenersAboutUpdatedElement(oldThing, newThing);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldThing == null) {
|
||||||
|
logger.debug("Refreshing thing \'{}\' after retry failed because thing is not found",
|
||||||
|
newThing.getUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newThing != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isThingHandlerFactoryReady(ThingHandlerFactory thingHandlerFactory) {
|
||||||
|
String bundleName = getBundleName(thingHandlerFactory);
|
||||||
|
return bundleName != null && loadedXmlThingTypes.contains(bundleName);
|
||||||
|
}
|
||||||
|
|
||||||
private @Nullable String getBundleName(ThingHandlerFactory thingHandlerFactory) {
|
private @Nullable String getBundleName(ThingHandlerFactory thingHandlerFactory) {
|
||||||
Bundle bundle = bundleResolver.resolveBundle(thingHandlerFactory.getClass());
|
Bundle bundle = bundleResolver.resolveBundle(thingHandlerFactory.getClass());
|
||||||
return bundle == null ? null : bundle.getSymbolicName();
|
return bundle == null ? null : bundle.getSymbolicName();
|
||||||
|
@ -300,20 +330,6 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
String[] segments = thingUID.getAsString().split(AbstractUID.SEPARATOR);
|
String[] segments = thingUID.getAsString().split(AbstractUID.SEPARATOR);
|
||||||
ThingTypeUID thingTypeUID = new ThingTypeUID(thingUID.getBindingId(), segments[1]);
|
ThingTypeUID thingTypeUID = new ThingTypeUID(thingUID.getBindingId(), segments[1]);
|
||||||
|
|
||||||
ThingHandlerFactory handlerFactory = thingHandlerFactories.stream()
|
|
||||||
.filter(thf -> thf.supportsThingType(thingTypeUID)).findFirst().orElse(null);
|
|
||||||
if (handlerFactory == null) {
|
|
||||||
if (modelLoaded) {
|
|
||||||
logger.info("No ThingHandlerFactory found for thing {} (thing-type is {}). Deferring initialization.",
|
|
||||||
thingUID, thingTypeUID);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String bundleName = getBundleName(handlerFactory);
|
|
||||||
if (bundleName == null || !loadedXmlThingTypes.contains(bundleName)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID, localeProvider.getLocale());
|
ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID, localeProvider.getLocale());
|
||||||
ThingUID bridgeUID = thingDto.bridge != null ? new ThingUID(thingDto.bridge) : null;
|
ThingUID bridgeUID = thingDto.bridge != null ? new ThingUID(thingDto.bridge) : null;
|
||||||
Configuration configuration = new Configuration(thingDto.config);
|
Configuration configuration = new Configuration(thingDto.config);
|
||||||
|
@ -333,22 +349,22 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
|
|
||||||
Thing thing = thingBuilder.build();
|
Thing thing = thingBuilder.build();
|
||||||
|
|
||||||
Thing thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
Thing thingFromHandler = null;
|
||||||
if (thingFromHandler != null) {
|
ThingHandlerFactory handlerFactory = thingHandlerFactories.stream()
|
||||||
mergeThing(thingFromHandler, thing);
|
.filter(thf -> isThingHandlerFactoryReady(thf) && thf.supportsThingType(thingTypeUID)).findFirst()
|
||||||
logger.debug("Successfully loaded thing \'{}\'", thingUID);
|
.orElse(null);
|
||||||
} else {
|
if (handlerFactory != null) {
|
||||||
// Possible cause: Asynchronous loading of the XML files
|
thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||||
// Add the data to the queue in order to retry it later
|
if (thingFromHandler != null) {
|
||||||
logger.debug(
|
mergeThing(thingFromHandler, thing);
|
||||||
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
|
logger.debug("Successfully loaded thing \'{}\'", thingUID);
|
||||||
handlerFactory.getClass().getSimpleName(), thingTypeUID);
|
} else {
|
||||||
queue.add(new QueueContent(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID));
|
// Possible cause: Asynchronous loading of the XML files
|
||||||
Thread thread = lazyRetryThread;
|
// Add the data to the queue in order to retry it later
|
||||||
if (thread == null || !thread.isAlive()) {
|
logger.debug(
|
||||||
thread = new Thread(lazyRetryRunnable);
|
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
|
||||||
lazyRetryThread = thread;
|
handlerFactory.getClass().getSimpleName(), thingTypeUID);
|
||||||
thread.start();
|
queueRetryThingCreation(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +399,7 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
configDescriptionRegistry.getConfigDescription(descUriO));
|
configDescriptionRegistry.getConfigDescription(descUriO));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Channel type {} could not be found.", channelTypeUID);
|
logger.warn("Channel type {} could not be found for thing '{}'.", channelTypeUID, thingUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,7 +431,12 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeThing(Thing target, Thing source) {
|
private void mergeThing(Thing target, Thing source) {
|
||||||
target.setLabel(source.getLabel());
|
String label = source.getLabel();
|
||||||
|
if (label == null) {
|
||||||
|
ThingType thingType = thingTypeRegistry.getThingType(target.getThingTypeUID(), localeProvider.getLocale());
|
||||||
|
label = thingType != null ? thingType.getLabel() : null;
|
||||||
|
}
|
||||||
|
target.setLabel(label);
|
||||||
target.setLocation(source.getLocation());
|
target.setLocation(source.getLocation());
|
||||||
target.setBridgeUID(source.getBridgeUID());
|
target.setBridgeUID(source.getBridgeUID());
|
||||||
|
|
||||||
|
@ -439,4 +460,15 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
||||||
// add the channels only defined in source list to the target list
|
// add the channels only defined in source list to the target list
|
||||||
ThingHelper.addChannelsToThing(target, channelsToAdd);
|
ThingHelper.addChannelsToThing(target, channelsToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void queueRetryThingCreation(ThingHandlerFactory handlerFactory, ThingTypeUID thingTypeUID,
|
||||||
|
Configuration configuration, ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||||
|
queue.add(new QueueContent(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID));
|
||||||
|
Thread thread = lazyRetryThread;
|
||||||
|
if (thread == null || !thread.isAlive()) {
|
||||||
|
thread = new Thread(lazyRetryRunnable);
|
||||||
|
lazyRetryThread = thread;
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue