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.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
* @author Laurent Garnier - Added methods refreshModelElements and generateSyntaxFromElements
|
||||
* @author Laurent Garnier - Added method generateSyntaxFromElements
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface YamlModelRepository {
|
||||
|
@ -31,14 +31,6 @@ public interface YamlModelRepository {
|
|||
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -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 Laurent Garnier - Introduce version 2 using map instead of table
|
||||
* @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
|
||||
*/
|
||||
@NonNullByDefault
|
||||
|
@ -564,35 +564,6 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
|
|||
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) {
|
||||
YamlModelWrapper model = modelCache.get(modelName);
|
||||
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.Configuration;
|
||||
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.YamlModelRepository;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
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 YamlModelRepository modelRepository;
|
||||
private final BundleResolver bundleResolver;
|
||||
private final ThingTypeRegistry thingTypeRegistry;
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
|
@ -104,32 +101,8 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
logger.debug("Starting lazy retry thread");
|
||||
while (!queue.isEmpty()) {
|
||||
for (QueueContent qc : queue) {
|
||||
logger.trace("Retry creating thing {}", qc.thingUID);
|
||||
Thing newThing = qc.thingHandlerFactory.createThing(qc.thingTypeUID, qc.configuration, qc.thingUID,
|
||||
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());
|
||||
}
|
||||
if (retryCreateThing(qc.thingHandlerFactory, qc.thingTypeUID, qc.configuration, qc.thingUID,
|
||||
qc.bridgeUID)) {
|
||||
queue.remove(qc);
|
||||
}
|
||||
}
|
||||
|
@ -153,12 +126,11 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
}
|
||||
|
||||
@Activate
|
||||
public YamlThingProvider(final @Reference YamlModelRepository modelRepository,
|
||||
final @Reference BundleResolver bundleResolver, final @Reference ThingTypeRegistry thingTypeRegistry,
|
||||
public YamlThingProvider(final @Reference BundleResolver bundleResolver,
|
||||
final @Reference ThingTypeRegistry thingTypeRegistry,
|
||||
final @Reference ChannelTypeRegistry channelTypeRegistry,
|
||||
final @Reference ConfigDescriptionRegistry configDescriptionRegistry,
|
||||
final @Reference LocaleProvider localeProvider) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.bundleResolver = bundleResolver;
|
||||
this.thingTypeRegistry = thingTypeRegistry;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
|
@ -242,6 +214,7 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addThingHandlerFactory(final ThingHandlerFactory thingHandlerFactory) {
|
||||
logger.debug("addThingHandlerFactory {}", thingHandlerFactory.getClass().getSimpleName());
|
||||
thingHandlerFactories.add(thingHandlerFactory);
|
||||
thingHandlerFactoryAdded(thingHandlerFactory);
|
||||
}
|
||||
|
@ -278,18 +251,75 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
loadedXmlThingTypes.remove(readyMarker.getIdentifier());
|
||||
}
|
||||
|
||||
private void thingHandlerFactoryAdded(ThingHandlerFactory thingHandlerFactory) {
|
||||
String bundleName = getBundleName(thingHandlerFactory);
|
||||
if (bundleName != null && loadedXmlThingTypes.contains(bundleName)) {
|
||||
logger.debug("Refreshing models due to new thing handler factory {}",
|
||||
thingHandlerFactory.getClass().getSimpleName());
|
||||
thingsMap.keySet().forEach(modelName -> {
|
||||
modelRepository.refreshModelElements(modelName,
|
||||
getElementClass().getAnnotation(YamlElementName.class).value());
|
||||
});
|
||||
private void thingHandlerFactoryAdded(ThingHandlerFactory handlerFactory) {
|
||||
logger.debug("thingHandlerFactoryAdded {} isThingHandlerFactoryReady={}",
|
||||
handlerFactory.getClass().getSimpleName(), isThingHandlerFactoryReady(handlerFactory));
|
||||
if (isThingHandlerFactoryReady(handlerFactory)) {
|
||||
if (!thingsMap.isEmpty()) {
|
||||
logger.debug("Refreshing models due to new thing handler factory {}",
|
||||
handlerFactory.getClass().getSimpleName());
|
||||
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) {
|
||||
Bundle bundle = bundleResolver.resolveBundle(thingHandlerFactory.getClass());
|
||||
return bundle == null ? null : bundle.getSymbolicName();
|
||||
|
@ -300,20 +330,6 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
String[] segments = thingUID.getAsString().split(AbstractUID.SEPARATOR);
|
||||
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());
|
||||
ThingUID bridgeUID = thingDto.bridge != null ? new ThingUID(thingDto.bridge) : null;
|
||||
Configuration configuration = new Configuration(thingDto.config);
|
||||
|
@ -333,22 +349,22 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
|
||||
Thing thing = thingBuilder.build();
|
||||
|
||||
Thing thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||
if (thingFromHandler != null) {
|
||||
mergeThing(thingFromHandler, thing);
|
||||
logger.debug("Successfully loaded thing \'{}\'", thingUID);
|
||||
} else {
|
||||
// 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(), thingTypeUID);
|
||||
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();
|
||||
Thing thingFromHandler = null;
|
||||
ThingHandlerFactory handlerFactory = thingHandlerFactories.stream()
|
||||
.filter(thf -> isThingHandlerFactoryReady(thf) && thf.supportsThingType(thingTypeUID)).findFirst()
|
||||
.orElse(null);
|
||||
if (handlerFactory != null) {
|
||||
thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
|
||||
if (thingFromHandler != null) {
|
||||
mergeThing(thingFromHandler, thing);
|
||||
logger.debug("Successfully loaded thing \'{}\'", thingUID);
|
||||
} else {
|
||||
// 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(), thingTypeUID);
|
||||
queueRetryThingCreation(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,7 +399,7 @@ public class YamlThingProvider extends AbstractProvider<Thing>
|
|||
configDescriptionRegistry.getConfigDescription(descUriO));
|
||||
}
|
||||
} 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) {
|
||||
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.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
|
||||
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