Skip to content

Commit 3df3375

Browse files
authored
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 <[email protected]>
1 parent fd171e2 commit 3df3375

File tree

3 files changed

+107
-112
lines changed

3 files changed

+107
-112
lines changed

bundles/org.openhab.core.model.yaml/src/main/java/org/openhab/core/model/yaml/YamlModelRepository.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* The {@link YamlModelRepository} defines methods to update elements in a YAML model.
2222
*
2323
* @author Jan N. Klug - Initial contribution
24-
* @author Laurent Garnier - Added methods refreshModelElements and generateSyntaxFromElements
24+
* @author Laurent Garnier - Added method generateSyntaxFromElements
2525
*/
2626
@NonNullByDefault
2727
public interface YamlModelRepository {
@@ -31,14 +31,6 @@ public interface YamlModelRepository {
3131

3232
void updateElementInModel(String modelName, YamlElement element);
3333

34-
/**
35-
* Triggers the refresh of a certain type of elements in a given model.
36-
*
37-
* @param modelName the model name
38-
* @param elementName the type of elements to refresh
39-
*/
40-
void refreshModelElements(String modelName, String elementName);
41-
4234
/**
4335
* Generate the YAML syntax from a provided list of elements.
4436
*

bundles/org.openhab.core.model.yaml/src/main/java/org/openhab/core/model/yaml/internal/YamlModelRepositoryImpl.java

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
* @author Jan N. Klug - Refactored for multiple types per file and add modifying possibility
7878
* @author Laurent Garnier - Introduce version 2 using map instead of table
7979
* @author Laurent Garnier - Added basic version management
80-
* @author Laurent Garnier - Added methods refreshModelElements and generateSyntaxFromElements + new parameters
80+
* @author Laurent Garnier - Added method generateSyntaxFromElements + new parameters
8181
* for method isValid
8282
*/
8383
@NonNullByDefault
@@ -564,35 +564,6 @@ public void updateElementInModel(String modelName, YamlElement element) {
564564
writeModel(modelName);
565565
}
566566

567-
@Override
568-
@SuppressWarnings({ "rawtypes", "unchecked" })
569-
public void refreshModelElements(String modelName, String elementName) {
570-
logger.info("Refreshing {} from YAML model {}", elementName, modelName);
571-
YamlModelWrapper model = modelCache.get(modelName);
572-
if (model == null) {
573-
logger.warn("Failed to refresh model {} because it is not known.", modelName);
574-
return;
575-
}
576-
577-
List<JsonNode> modelNodes = model.getNodesV1().get(elementName);
578-
JsonNode modelMapNode = model.getNodes().get(elementName);
579-
if (modelNodes == null && modelMapNode == null) {
580-
logger.warn("Failed to refresh model {} because type {} is not known in the model.", modelName,
581-
elementName);
582-
return;
583-
}
584-
585-
getElementListeners(elementName, model.getVersion()).forEach(listener -> {
586-
Class<? extends YamlElement> elementClass = listener.getElementClass();
587-
588-
List elements = parseJsonNodes(modelNodes != null ? modelNodes : List.of(), modelMapNode, elementClass,
589-
null, null);
590-
if (!elements.isEmpty()) {
591-
listener.updatedModel(modelName, elements);
592-
}
593-
});
594-
}
595-
596567
private void writeModel(String modelName) {
597568
YamlModelWrapper model = modelCache.get(modelName);
598569
if (model == null) {

bundles/org.openhab.core.model.yaml/src/main/java/org/openhab/core/model/yaml/internal/things/YamlThingProvider.java

Lines changed: 105 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@
3232
import org.openhab.core.config.core.ConfigUtil;
3333
import org.openhab.core.config.core.Configuration;
3434
import org.openhab.core.i18n.LocaleProvider;
35-
import org.openhab.core.model.yaml.YamlElementName;
3635
import org.openhab.core.model.yaml.YamlModelListener;
37-
import org.openhab.core.model.yaml.YamlModelRepository;
3836
import org.openhab.core.service.ReadyMarker;
3937
import org.openhab.core.service.ReadyService;
4038
import org.openhab.core.service.StartLevelService;
@@ -84,7 +82,6 @@ public class YamlThingProvider extends AbstractProvider<Thing>
8482

8583
private final Logger logger = LoggerFactory.getLogger(YamlThingProvider.class);
8684

87-
private final YamlModelRepository modelRepository;
8885
private final BundleResolver bundleResolver;
8986
private final ThingTypeRegistry thingTypeRegistry;
9087
private final ChannelTypeRegistry channelTypeRegistry;
@@ -104,32 +101,8 @@ public void run() {
104101
logger.debug("Starting lazy retry thread");
105102
while (!queue.isEmpty()) {
106103
for (QueueContent qc : queue) {
107-
logger.trace("Retry creating thing {}", qc.thingUID);
108-
Thing newThing = qc.thingHandlerFactory.createThing(qc.thingTypeUID, qc.configuration, qc.thingUID,
109-
qc.bridgeUID);
110-
if (newThing != null) {
111-
logger.debug("Successfully loaded thing \'{}\' during retry", qc.thingUID);
112-
Thing oldThing = null;
113-
for (Map.Entry<String, Collection<Thing>> entry : thingsMap.entrySet()) {
114-
oldThing = entry.getValue().stream().filter(t -> t.getUID().equals(newThing.getUID()))
115-
.findFirst().orElse(null);
116-
if (oldThing != null) {
117-
mergeThing(newThing, oldThing);
118-
Collection<Thing> thingsForModel = Objects
119-
.requireNonNull(thingsMap.get(entry.getKey()));
120-
thingsForModel.remove(oldThing);
121-
thingsForModel.add(newThing);
122-
logger.debug("Refreshing thing \'{}\' after successful retry", newThing.getUID());
123-
if (!ThingHelper.equals(oldThing, newThing)) {
124-
notifyListenersAboutUpdatedElement(oldThing, newThing);
125-
}
126-
break;
127-
}
128-
}
129-
if (oldThing == null) {
130-
logger.debug("Refreshing thing \'{}\' after retry failed because thing is not found",
131-
newThing.getUID());
132-
}
104+
if (retryCreateThing(qc.thingHandlerFactory, qc.thingTypeUID, qc.configuration, qc.thingUID,
105+
qc.bridgeUID)) {
133106
queue.remove(qc);
134107
}
135108
}
@@ -153,12 +126,11 @@ private record QueueContent(ThingHandlerFactory thingHandlerFactory, ThingTypeUI
153126
}
154127

155128
@Activate
156-
public YamlThingProvider(final @Reference YamlModelRepository modelRepository,
157-
final @Reference BundleResolver bundleResolver, final @Reference ThingTypeRegistry thingTypeRegistry,
129+
public YamlThingProvider(final @Reference BundleResolver bundleResolver,
130+
final @Reference ThingTypeRegistry thingTypeRegistry,
158131
final @Reference ChannelTypeRegistry channelTypeRegistry,
159132
final @Reference ConfigDescriptionRegistry configDescriptionRegistry,
160133
final @Reference LocaleProvider localeProvider) {
161-
this.modelRepository = modelRepository;
162134
this.bundleResolver = bundleResolver;
163135
this.thingTypeRegistry = thingTypeRegistry;
164136
this.channelTypeRegistry = channelTypeRegistry;
@@ -242,6 +214,7 @@ public void removedModel(String modelName, Collection<YamlThingDTO> elements) {
242214

243215
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
244216
protected void addThingHandlerFactory(final ThingHandlerFactory thingHandlerFactory) {
217+
logger.debug("addThingHandlerFactory {}", thingHandlerFactory.getClass().getSimpleName());
245218
thingHandlerFactories.add(thingHandlerFactory);
246219
thingHandlerFactoryAdded(thingHandlerFactory);
247220
}
@@ -278,16 +251,73 @@ public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
278251
loadedXmlThingTypes.remove(readyMarker.getIdentifier());
279252
}
280253

281-
private void thingHandlerFactoryAdded(ThingHandlerFactory thingHandlerFactory) {
282-
String bundleName = getBundleName(thingHandlerFactory);
283-
if (bundleName != null && loadedXmlThingTypes.contains(bundleName)) {
284-
logger.debug("Refreshing models due to new thing handler factory {}",
285-
thingHandlerFactory.getClass().getSimpleName());
286-
thingsMap.keySet().forEach(modelName -> {
287-
modelRepository.refreshModelElements(modelName,
288-
getElementClass().getAnnotation(YamlElementName.class).value());
289-
});
254+
private void thingHandlerFactoryAdded(ThingHandlerFactory handlerFactory) {
255+
logger.debug("thingHandlerFactoryAdded {} isThingHandlerFactoryReady={}",
256+
handlerFactory.getClass().getSimpleName(), isThingHandlerFactoryReady(handlerFactory));
257+
if (isThingHandlerFactoryReady(handlerFactory)) {
258+
if (!thingsMap.isEmpty()) {
259+
logger.debug("Refreshing models due to new thing handler factory {}",
260+
handlerFactory.getClass().getSimpleName());
261+
thingsMap.keySet().forEach(modelName -> {
262+
List<Thing> things = thingsMap.getOrDefault(modelName, List.of()).stream()
263+
.filter(th -> handlerFactory.supportsThingType(th.getThingTypeUID())).toList();
264+
if (!things.isEmpty()) {
265+
logger.info("Refreshing YAML model {} ({} things with {})", modelName, things.size(),
266+
handlerFactory.getClass().getSimpleName());
267+
things.forEach(thing -> {
268+
if (!retryCreateThing(handlerFactory, thing.getThingTypeUID(), thing.getConfiguration(),
269+
thing.getUID(), thing.getBridgeUID())) {
270+
// Possible cause: Asynchronous loading of the XML files
271+
// Add the data to the queue in order to retry it later
272+
logger.debug(
273+
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
274+
handlerFactory.getClass().getSimpleName(), thing.getThingTypeUID());
275+
queueRetryThingCreation(handlerFactory, thing.getThingTypeUID(),
276+
thing.getConfiguration(), thing.getUID(), thing.getBridgeUID());
277+
}
278+
});
279+
} else {
280+
logger.debug("No refresh needed from YAML model {}", modelName);
281+
}
282+
});
283+
} else {
284+
logger.debug("No things yet loaded; no need to trigger a refresh due to new thing handler factory");
285+
}
286+
}
287+
}
288+
289+
private boolean retryCreateThing(ThingHandlerFactory handlerFactory, ThingTypeUID thingTypeUID,
290+
Configuration configuration, ThingUID thingUID, @Nullable ThingUID bridgeUID) {
291+
logger.trace("Retry creating thing {}", thingUID);
292+
Thing newThing = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
293+
if (newThing != null) {
294+
logger.debug("Successfully loaded thing \'{}\' during retry", thingUID);
295+
Thing oldThing = null;
296+
for (Collection<Thing> modelThings : thingsMap.values()) {
297+
oldThing = modelThings.stream().filter(t -> t.getUID().equals(newThing.getUID())).findFirst()
298+
.orElse(null);
299+
if (oldThing != null) {
300+
mergeThing(newThing, oldThing);
301+
modelThings.remove(oldThing);
302+
modelThings.add(newThing);
303+
logger.debug("Refreshing thing \'{}\' after successful retry", newThing.getUID());
304+
if (!ThingHelper.equals(oldThing, newThing)) {
305+
notifyListenersAboutUpdatedElement(oldThing, newThing);
306+
}
307+
break;
308+
}
309+
}
310+
if (oldThing == null) {
311+
logger.debug("Refreshing thing \'{}\' after retry failed because thing is not found",
312+
newThing.getUID());
313+
}
290314
}
315+
return newThing != null;
316+
}
317+
318+
private boolean isThingHandlerFactoryReady(ThingHandlerFactory thingHandlerFactory) {
319+
String bundleName = getBundleName(thingHandlerFactory);
320+
return bundleName != null && loadedXmlThingTypes.contains(bundleName);
291321
}
292322

293323
private @Nullable String getBundleName(ThingHandlerFactory thingHandlerFactory) {
@@ -300,20 +330,6 @@ private void thingHandlerFactoryAdded(ThingHandlerFactory thingHandlerFactory) {
300330
String[] segments = thingUID.getAsString().split(AbstractUID.SEPARATOR);
301331
ThingTypeUID thingTypeUID = new ThingTypeUID(thingUID.getBindingId(), segments[1]);
302332

303-
ThingHandlerFactory handlerFactory = thingHandlerFactories.stream()
304-
.filter(thf -> thf.supportsThingType(thingTypeUID)).findFirst().orElse(null);
305-
if (handlerFactory == null) {
306-
if (modelLoaded) {
307-
logger.info("No ThingHandlerFactory found for thing {} (thing-type is {}). Deferring initialization.",
308-
thingUID, thingTypeUID);
309-
}
310-
return null;
311-
}
312-
String bundleName = getBundleName(handlerFactory);
313-
if (bundleName == null || !loadedXmlThingTypes.contains(bundleName)) {
314-
return null;
315-
}
316-
317333
ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID, localeProvider.getLocale());
318334
ThingUID bridgeUID = thingDto.bridge != null ? new ThingUID(thingDto.bridge) : null;
319335
Configuration configuration = new Configuration(thingDto.config);
@@ -333,22 +349,22 @@ private void thingHandlerFactoryAdded(ThingHandlerFactory thingHandlerFactory) {
333349

334350
Thing thing = thingBuilder.build();
335351

336-
Thing thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
337-
if (thingFromHandler != null) {
338-
mergeThing(thingFromHandler, thing);
339-
logger.debug("Successfully loaded thing \'{}\'", thingUID);
340-
} else {
341-
// Possible cause: Asynchronous loading of the XML files
342-
// Add the data to the queue in order to retry it later
343-
logger.debug(
344-
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
345-
handlerFactory.getClass().getSimpleName(), thingTypeUID);
346-
queue.add(new QueueContent(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID));
347-
Thread thread = lazyRetryThread;
348-
if (thread == null || !thread.isAlive()) {
349-
thread = new Thread(lazyRetryRunnable);
350-
lazyRetryThread = thread;
351-
thread.start();
352+
Thing thingFromHandler = null;
353+
ThingHandlerFactory handlerFactory = thingHandlerFactories.stream()
354+
.filter(thf -> isThingHandlerFactoryReady(thf) && thf.supportsThingType(thingTypeUID)).findFirst()
355+
.orElse(null);
356+
if (handlerFactory != null) {
357+
thingFromHandler = handlerFactory.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
358+
if (thingFromHandler != null) {
359+
mergeThing(thingFromHandler, thing);
360+
logger.debug("Successfully loaded thing \'{}\'", thingUID);
361+
} else {
362+
// Possible cause: Asynchronous loading of the XML files
363+
// Add the data to the queue in order to retry it later
364+
logger.debug(
365+
"ThingHandlerFactory \'{}\' claimed it can handle \'{}\' type but actually did not. Queued for later refresh.",
366+
handlerFactory.getClass().getSimpleName(), thingTypeUID);
367+
queueRetryThingCreation(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID);
352368
}
353369
}
354370

@@ -383,7 +399,7 @@ private List<Channel> createChannels(ThingTypeUID thingTypeUID, ThingUID thingUI
383399
configDescriptionRegistry.getConfigDescription(descUriO));
384400
}
385401
} else {
386-
logger.warn("Channel type {} could not be found.", channelTypeUID);
402+
logger.warn("Channel type {} could not be found for thing '{}'.", channelTypeUID, thingUID);
387403
}
388404
}
389405

@@ -415,7 +431,12 @@ private List<Channel> createChannels(ThingTypeUID thingTypeUID, ThingUID thingUI
415431
}
416432

417433
private void mergeThing(Thing target, Thing source) {
418-
target.setLabel(source.getLabel());
434+
String label = source.getLabel();
435+
if (label == null) {
436+
ThingType thingType = thingTypeRegistry.getThingType(target.getThingTypeUID(), localeProvider.getLocale());
437+
label = thingType != null ? thingType.getLabel() : null;
438+
}
439+
target.setLabel(label);
419440
target.setLocation(source.getLocation());
420441
target.setBridgeUID(source.getBridgeUID());
421442

@@ -439,4 +460,15 @@ private void mergeThing(Thing target, Thing source) {
439460
// add the channels only defined in source list to the target list
440461
ThingHelper.addChannelsToThing(target, channelsToAdd);
441462
}
463+
464+
private void queueRetryThingCreation(ThingHandlerFactory handlerFactory, ThingTypeUID thingTypeUID,
465+
Configuration configuration, ThingUID thingUID, @Nullable ThingUID bridgeUID) {
466+
queue.add(new QueueContent(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID));
467+
Thread thread = lazyRetryThread;
468+
if (thread == null || !thread.isAlive()) {
469+
thread = new Thread(lazyRetryRunnable);
470+
lazyRetryThread = thread;
471+
thread.start();
472+
}
473+
}
442474
}

0 commit comments

Comments
 (0)