Skip to content

Commit f0c6780

Browse files
committed
YAML things provider: create things even if binding is not yet installed
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 26167c9 commit f0c6780

File tree

3 files changed

+101
-111
lines changed

3 files changed

+101
-111
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
@@ -565,35 +565,6 @@ public void updateElementInModel(String modelName, YamlElement element) {
565565
writeModel(modelName);
566566
}
567567

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

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

Lines changed: 99 additions & 72 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

@@ -439,4 +455,15 @@ private void mergeThing(Thing target, Thing source) {
439455
// add the channels only defined in source list to the target list
440456
ThingHelper.addChannelsToThing(target, channelsToAdd);
441457
}
458+
459+
private void queueRetryThingCreation(ThingHandlerFactory handlerFactory, ThingTypeUID thingTypeUID,
460+
Configuration configuration, ThingUID thingUID, @Nullable ThingUID bridgeUID) {
461+
queue.add(new QueueContent(handlerFactory, thingTypeUID, configuration, thingUID, bridgeUID));
462+
Thread thread = lazyRetryThread;
463+
if (thread == null || !thread.isAlive()) {
464+
thread = new Thread(lazyRetryRunnable);
465+
lazyRetryThread = thread;
466+
thread.start();
467+
}
468+
}
442469
}

0 commit comments

Comments
 (0)