From a304edd56dcc21a00074a17cb03748a99335124c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 17 Sep 2023 20:22:40 +0100 Subject: [PATCH 01/98] [suggestion-finder] initial contribution Signed-off-by: Andrew Fiddian-Green --- bom/openhab-core/pom.xml | 6 ++ .../.classpath | 30 ++++++++ .../.project | 23 ++++++ .../NOTICE | 14 ++++ .../pom.xml | 30 ++++++++ .../discovery/AddonSuggestionParticipant.java | 53 ++++++++++++++ .../MdnsAddonSuggestionParticipant.java | 73 +++++++++++++++++++ .../UpnpAddonSuggestionParticipant.java | 64 ++++++++++++++++ .../addon/finder/AddonSuggestionFinder.java | 59 +++++++++++++++ .../addon/finder/AddonSuggestionListener.java | 24 ++++++ bundles/pom.xml | 1 + 11 files changed, 377 insertions(+) create mode 100644 bundles/org.openhab.core.config.discovery.addon/.classpath create mode 100644 bundles/org.openhab.core.config.discovery.addon/.project create mode 100644 bundles/org.openhab.core.config.discovery.addon/NOTICE create mode 100644 bundles/org.openhab.core.config.discovery.addon/pom.xml create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml index e8f72da9320..349e93c04d9 100644 --- a/bom/openhab-core/pom.xml +++ b/bom/openhab-core/pom.xml @@ -304,6 +304,12 @@ ${project.version} compile + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + compile + org.openhab.core.bundles org.openhab.core.config.discovery.mdns diff --git a/bundles/org.openhab.core.config.discovery.addon/.classpath b/bundles/org.openhab.core.config.discovery.addon/.classpath new file mode 100644 index 00000000000..8631a3aebc7 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/.classpath @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/.project b/bundles/org.openhab.core.config.discovery.addon/.project new file mode 100644 index 00000000000..ea7024024f2 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.config.discovery.addon + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.core.config.discovery.addon/NOTICE b/bundles/org.openhab.core.config.discovery.addon/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml new file mode 100644 index 00000000000..797d8751d9a --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + + org.openhab.core.bundles + org.openhab.core.reactor.bundles + 4.1.0-SNAPSHOT + + + org.openhab.core.config.discovery.addon + + openHAB Core :: Bundles :: Addon Suggestion Finder + + + + org.openhab.core.bundles + org.openhab.core.config.discovery.mdns + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.config.discovery.upnp + ${project.version} + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java new file mode 100644 index 00000000000..a7685372efa --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java @@ -0,0 +1,53 @@ +/** + * 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.config.discovery.addon.discovery; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; + +/** + * This is a {@link AddonSuggestionParticipant} base class for discovery + * participants to find suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonSuggestionParticipant { + protected AddonSuggestionListener listener; + protected final String bindingId; + protected final Map propertyMatchRegexMap = new HashMap<>(); + + public AddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegexMap) { + this.listener = listener; + this.bindingId = bindingId; + this.propertyMatchRegexMap.putAll(propertyMatchRegexMap); + } + + /** + * Check if the given property name is in the propertyMatchRegexMap and the + * given property value matches the respective regular expression. + * + * @param propertyName + * @param propertyValue + * @return true a) if the property name exists and the property value matches + * the regular expression, or b) the property name does not exist. + */ + protected boolean isPropertyValid(String propertyName, String propertyValue) { + String matchRegex = propertyMatchRegexMap.get(propertyName); + return matchRegex != null ? propertyValue.matches(matchRegex) : true; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java new file mode 100644 index 00000000000..6be03f924fb --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java @@ -0,0 +1,73 @@ +/** + * 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.config.discovery.addon.discovery; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; +import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; + +/** + * This is a {@link MdnsAddonSuggestionParticipant} mdns version of class for + * discovery participants to find suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "discovery.mdns.suggestion.finder") +public class MdnsAddonSuggestionParticipant extends AddonSuggestionParticipant implements MDNSDiscoveryParticipant { + private final String serviceType; + + public MdnsAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegex, String serviceType) { + super(listener, bindingId, propertyMatchRegex); + this.serviceType = serviceType; + } + + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(); + } + + @Override + public String getServiceType() { + return serviceType; + } + + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { + boolean ok = isPropertyValid("APPLICATION", serviceInfo.getApplication()) // + && isPropertyValid("NAME", serviceInfo.getName()); + ok &= Collections.list(serviceInfo.getPropertyNames()).stream() + .allMatch(propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName))); + if (ok) { + listener.onAddonSuggestionFound(bindingId); + } + return null; + } + + @Override + public @Nullable ThingUID getThingUID(ServiceInfo service) { + return null; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java new file mode 100644 index 00000000000..5cdbd38daa6 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java @@ -0,0 +1,64 @@ +/** + * 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.config.discovery.addon.discovery; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.jupnp.model.meta.RemoteDevice; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; +import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; + +/** + * This is a {@link UpnpAddonSuggestionParticipant} upnp version of class for + * discovery participants to find suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(service = UpnpDiscoveryParticipant.class, configurationPid = "discovery.upnp.suggestion.finder") +public class UpnpAddonSuggestionParticipant extends AddonSuggestionParticipant implements UpnpDiscoveryParticipant { + + public UpnpAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegexMap) { + super(listener, bindingId, propertyMatchRegexMap); + } + + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(); + } + + @Override + public @Nullable DiscoveryResult createResult(RemoteDevice device) { + if (isPropertyValid("deviceType", device.getType().getType()) + && isPropertyValid("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) + && isPropertyValid("model", device.getDetails().getModelDetails().getModelName()) + && isPropertyValid("serialNumber", device.getDetails().getSerialNumber()) + && isPropertyValid("udn", device.getIdentity().getUdn().getIdentifierString())) { + listener.onAddonSuggestionFound(bindingId); + } + return null; + } + + @Override + public @Nullable ThingUID getThingUID(RemoteDevice device) { + return null; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java new file mode 100644 index 00000000000..42860c5a502 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -0,0 +1,59 @@ +/** + * 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.config.discovery.addon.finder; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.addon.discovery.AddonSuggestionParticipant; +import org.openhab.core.config.discovery.addon.discovery.MdnsAddonSuggestionParticipant; +import org.openhab.core.config.discovery.addon.discovery.UpnpAddonSuggestionParticipant; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a {@link AddonSuggestionFinder} which discovers suggested bindings to install. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component +public class AddonSuggestionFinder implements AddonSuggestionListener { + + private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); + + private final Set bindingIds = new HashSet<>(); + private final List addonSuggestionParticipants = new ArrayList<>(); + + protected void initialize() { + AddonSuggestionParticipant mdnsParticipant = new MdnsAddonSuggestionParticipant(this, "aaa", Map.of(), "bbb"); + AddonSuggestionParticipant upnpParticipant = new UpnpAddonSuggestionParticipant(this, "ccc", Map.of()); + addonSuggestionParticipants.add(mdnsParticipant); + addonSuggestionParticipants.add(upnpParticipant); + } + + protected void dispose() { + addonSuggestionParticipants.clear(); + } + + @Override + public void onAddonSuggestionFound(String bindingId) { + logger.debug("found binding id:{}", bindingId); + bindingIds.add(bindingId); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java new file mode 100644 index 00000000000..438bbb584de --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java @@ -0,0 +1,24 @@ +/** + * 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.config.discovery.addon.finder; + +/** + * This is a {@link AddonSuggestionListener} which is a callback for discovery of suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +public interface AddonSuggestionListener { + + public void onAddonSuggestionFound(String bindingId); + +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 70000056d92..a6bfd0d30f8 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -30,6 +30,7 @@ org.openhab.core.automation.rest org.openhab.core.config.core org.openhab.core.config.discovery + org.openhab.core.config.discovery.addon org.openhab.core.config.discovery.mdns org.openhab.core.config.discovery.usbserial org.openhab.core.config.discovery.usbserial.linuxsysfs From 5350ba263146a7cf077c70d31c290d0696e5447e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 17 Sep 2023 22:42:54 +0100 Subject: [PATCH 02/98] [suggestion-finder] tweaks Signed-off-by: Andrew Fiddian-Green --- .../addon/discovery/MdnsAddonSuggestionParticipant.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java index 6be03f924fb..b815ef131e2 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java @@ -56,11 +56,10 @@ public String getServiceType() { @Override public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { - boolean ok = isPropertyValid("APPLICATION", serviceInfo.getApplication()) // - && isPropertyValid("NAME", serviceInfo.getName()); - ok &= Collections.list(serviceInfo.getPropertyNames()).stream() - .allMatch(propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName))); - if (ok) { + if (isPropertyValid("application", serviceInfo.getApplication()) + && isPropertyValid("name", serviceInfo.getName()) + && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( + propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName)))) { listener.onAddonSuggestionFound(bindingId); } return null; From 547d0b1148974bbc49f3dd1b0300c12a4555f5ae Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 17 Sep 2023 22:56:15 +0100 Subject: [PATCH 03/98] [suggestion-finder] spotless Signed-off-by: Andrew Fiddian-Green --- .../.classpath | 5 +- .../discovery/AddonSuggestionParticipant.java | 44 +++++++-------- .../MdnsAddonSuggestionParticipant.java | 56 +++++++++---------- .../UpnpAddonSuggestionParticipant.java | 46 +++++++-------- .../addon/finder/AddonSuggestionFinder.java | 42 +++++++------- .../addon/finder/AddonSuggestionListener.java | 6 +- 6 files changed, 100 insertions(+), 99 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/.classpath b/bundles/org.openhab.core.config.discovery.addon/.classpath index 8631a3aebc7..e9d63b05acb 100644 --- a/bundles/org.openhab.core.config.discovery.addon/.classpath +++ b/bundles/org.openhab.core.config.discovery.addon/.classpath @@ -8,15 +8,14 @@ - - + - + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java index a7685372efa..bd5ccb8281d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java @@ -26,28 +26,28 @@ */ @NonNullByDefault public class AddonSuggestionParticipant { - protected AddonSuggestionListener listener; - protected final String bindingId; - protected final Map propertyMatchRegexMap = new HashMap<>(); + protected AddonSuggestionListener listener; + protected final String bindingId; + protected final Map propertyMatchRegexMap = new HashMap<>(); - public AddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegexMap) { - this.listener = listener; - this.bindingId = bindingId; - this.propertyMatchRegexMap.putAll(propertyMatchRegexMap); - } + public AddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegexMap) { + this.listener = listener; + this.bindingId = bindingId; + this.propertyMatchRegexMap.putAll(propertyMatchRegexMap); + } - /** - * Check if the given property name is in the propertyMatchRegexMap and the - * given property value matches the respective regular expression. - * - * @param propertyName - * @param propertyValue - * @return true a) if the property name exists and the property value matches - * the regular expression, or b) the property name does not exist. - */ - protected boolean isPropertyValid(String propertyName, String propertyValue) { - String matchRegex = propertyMatchRegexMap.get(propertyName); - return matchRegex != null ? propertyValue.matches(matchRegex) : true; - } + /** + * Check if the given property name is in the propertyMatchRegexMap and the + * given property value matches the respective regular expression. + * + * @param propertyName + * @param propertyValue + * @return true a) if the property name exists and the property value matches + * the regular expression, or b) the property name does not exist. + */ + protected boolean isPropertyValid(String propertyName, String propertyValue) { + String matchRegex = propertyMatchRegexMap.get(propertyName); + return matchRegex != null ? propertyValue.matches(matchRegex) : true; + } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java index b815ef131e2..42443254bf5 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java @@ -36,37 +36,37 @@ @NonNullByDefault @Component(service = MDNSDiscoveryParticipant.class, configurationPid = "discovery.mdns.suggestion.finder") public class MdnsAddonSuggestionParticipant extends AddonSuggestionParticipant implements MDNSDiscoveryParticipant { - private final String serviceType; + private final String serviceType; - public MdnsAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegex, String serviceType) { - super(listener, bindingId, propertyMatchRegex); - this.serviceType = serviceType; - } + public MdnsAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegex, String serviceType) { + super(listener, bindingId, propertyMatchRegex); + this.serviceType = serviceType; + } - @Override - public Set getSupportedThingTypeUIDs() { - return Set.of(); - } + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(); + } - @Override - public String getServiceType() { - return serviceType; - } + @Override + public String getServiceType() { + return serviceType; + } - @Override - public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { - if (isPropertyValid("application", serviceInfo.getApplication()) - && isPropertyValid("name", serviceInfo.getName()) - && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( - propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName)))) { - listener.onAddonSuggestionFound(bindingId); - } - return null; - } + @Override + public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { + if (isPropertyValid("application", serviceInfo.getApplication()) + && isPropertyValid("name", serviceInfo.getName()) + && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( + propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName)))) { + listener.onAddonSuggestionFound(bindingId); + } + return null; + } - @Override - public @Nullable ThingUID getThingUID(ServiceInfo service) { - return null; - } + @Override + public @Nullable ThingUID getThingUID(ServiceInfo service) { + return null; + } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java index 5cdbd38daa6..fb072da6b46 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java @@ -35,30 +35,30 @@ @Component(service = UpnpDiscoveryParticipant.class, configurationPid = "discovery.upnp.suggestion.finder") public class UpnpAddonSuggestionParticipant extends AddonSuggestionParticipant implements UpnpDiscoveryParticipant { - public UpnpAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegexMap) { - super(listener, bindingId, propertyMatchRegexMap); - } + public UpnpAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, + Map propertyMatchRegexMap) { + super(listener, bindingId, propertyMatchRegexMap); + } - @Override - public Set getSupportedThingTypeUIDs() { - return Set.of(); - } + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(); + } - @Override - public @Nullable DiscoveryResult createResult(RemoteDevice device) { - if (isPropertyValid("deviceType", device.getType().getType()) - && isPropertyValid("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) - && isPropertyValid("model", device.getDetails().getModelDetails().getModelName()) - && isPropertyValid("serialNumber", device.getDetails().getSerialNumber()) - && isPropertyValid("udn", device.getIdentity().getUdn().getIdentifierString())) { - listener.onAddonSuggestionFound(bindingId); - } - return null; - } + @Override + public @Nullable DiscoveryResult createResult(RemoteDevice device) { + if (isPropertyValid("deviceType", device.getType().getType()) + && isPropertyValid("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) + && isPropertyValid("model", device.getDetails().getModelDetails().getModelName()) + && isPropertyValid("serialNumber", device.getDetails().getSerialNumber()) + && isPropertyValid("udn", device.getIdentity().getUdn().getIdentifierString())) { + listener.onAddonSuggestionFound(bindingId); + } + return null; + } - @Override - public @Nullable ThingUID getThingUID(RemoteDevice device) { - return null; - } + @Override + public @Nullable ThingUID getThingUID(RemoteDevice device) { + return null; + } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index 42860c5a502..da017b4a650 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -35,25 +35,25 @@ @Component public class AddonSuggestionFinder implements AddonSuggestionListener { - private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); - - private final Set bindingIds = new HashSet<>(); - private final List addonSuggestionParticipants = new ArrayList<>(); - - protected void initialize() { - AddonSuggestionParticipant mdnsParticipant = new MdnsAddonSuggestionParticipant(this, "aaa", Map.of(), "bbb"); - AddonSuggestionParticipant upnpParticipant = new UpnpAddonSuggestionParticipant(this, "ccc", Map.of()); - addonSuggestionParticipants.add(mdnsParticipant); - addonSuggestionParticipants.add(upnpParticipant); - } - - protected void dispose() { - addonSuggestionParticipants.clear(); - } - - @Override - public void onAddonSuggestionFound(String bindingId) { - logger.debug("found binding id:{}", bindingId); - bindingIds.add(bindingId); - } + private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); + + private final Set bindingIds = new HashSet<>(); + private final List addonSuggestionParticipants = new ArrayList<>(); + + protected void initialize() { + AddonSuggestionParticipant mdnsParticipant = new MdnsAddonSuggestionParticipant(this, "aaa", Map.of(), "bbb"); + AddonSuggestionParticipant upnpParticipant = new UpnpAddonSuggestionParticipant(this, "ccc", Map.of()); + addonSuggestionParticipants.add(mdnsParticipant); + addonSuggestionParticipants.add(upnpParticipant); + } + + protected void dispose() { + addonSuggestionParticipants.clear(); + } + + @Override + public void onAddonSuggestionFound(String bindingId) { + logger.debug("found binding id:{}", bindingId); + bindingIds.add(bindingId); + } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java index 438bbb584de..f452e20727e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java @@ -12,13 +12,15 @@ */ package org.openhab.core.config.discovery.addon.finder; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * This is a {@link AddonSuggestionListener} which is a callback for discovery of suggested addons. * * @author Andrew Fiddian-Green - Initial contribution */ +@NonNullByDefault public interface AddonSuggestionListener { - - public void onAddonSuggestionFound(String bindingId); + public void onAddonSuggestionFound(String bindingId); } From f9e9da39a6e803b81aa04c223b9cbdc1c91a10dc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 18 Sep 2023 17:06:26 +0100 Subject: [PATCH 04/98] [suggestion-finder] second attempt Signed-off-by: Andrew Fiddian-Green --- .../BaseCandidate.java} | 31 ++-- .../addon/candidate/MdnsCandidate.java | 55 ++++++ .../addon/candidate/UpnpCandidate.java | 49 ++++++ .../MdnsAddonSuggestionParticipant.java | 72 -------- .../UpnpAddonSuggestionParticipant.java | 64 ------- .../config/discovery/addon/dto/Candidate.java | 71 ++++++++ .../discovery/addon/dto/Candidates.java | 36 ++++ .../DiscoveryType.java} | 10 +- .../discovery/addon/dto/PropertyRegex.java | 39 +++++ .../addon/finder/AddonSuggestionFinder.java | 165 +++++++++++++++--- .../addon/xml/CandidatesSerializer.java | 50 ++++++ 11 files changed, 466 insertions(+), 176 deletions(-) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/{discovery/AddonSuggestionParticipant.java => candidate/BaseCandidate.java} (54%) create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/{finder/AddonSuggestionListener.java => dto/DiscoveryType.java} (66%) create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java similarity index 54% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java index bd5ccb8281d..31818156ac7 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/AddonSuggestionParticipant.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java @@ -10,44 +10,45 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.discovery; +package org.openhab.core.config.discovery.addon.candidate; import java.util.HashMap; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; /** - * This is a {@link AddonSuggestionParticipant} base class for discovery - * participants to find suggested addons. + * This is a {@link BaseCandidate} abstract base class for candidates + * for suggested addons. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class AddonSuggestionParticipant { - protected AddonSuggestionListener listener; - protected final String bindingId; +public abstract class BaseCandidate { + protected final String addonId; protected final Map propertyMatchRegexMap = new HashMap<>(); - public AddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegexMap) { - this.listener = listener; - this.bindingId = bindingId; + public BaseCandidate(String addonId, Map propertyMatchRegexMap) { + this.addonId = addonId; this.propertyMatchRegexMap.putAll(propertyMatchRegexMap); } + public String getAddonId() { + return addonId; + } + /** - * Check if the given property name is in the propertyMatchRegexMap and the - * given property value matches the respective regular expression. + * Helper method to check if the given property name is in the + * propertyMatchRegexMap and the given property value matches the respective + * regular expression. * * @param propertyName * @param propertyValue * @return true a) if the property name exists and the property value matches * the regular expression, or b) the property name does not exist. */ - protected boolean isPropertyValid(String propertyName, String propertyValue) { + protected boolean propertyMatches(String propertyName, String propertyValue) { String matchRegex = propertyMatchRegexMap.get(propertyName); - return matchRegex != null ? propertyValue.matches(matchRegex) : true; + return matchRegex == null ? true : propertyValue.matches(matchRegex); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java new file mode 100644 index 00000000000..5e56c88e344 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java @@ -0,0 +1,55 @@ +/** + * 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.config.discovery.addon.candidate; + +import java.util.Collections; +import java.util.Map; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * This is a {@link MdnsCandidate} mdns version of class for candidates for + * suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class MdnsCandidate extends BaseCandidate { + private final String mdnsServiceType; + + public MdnsCandidate(String bindingId, Map propertyMatchRegex, String serviceType) { + super(bindingId, propertyMatchRegex); + this.mdnsServiceType = serviceType; + } + + /** + * Check if the data in the provided ServiceInfo matches our own match criteria. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof ServiceInfo serviceInfo) { + return propertyMatches("application", serviceInfo.getApplication()) + && propertyMatches("name", serviceInfo.getName()) + && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( + propertyName -> propertyMatches(propertyName, serviceInfo.getPropertyString(propertyName))); + } + return false; + } + + public String getMdnsServiceType() { + return mdnsServiceType; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java new file mode 100644 index 00000000000..15c79b7a055 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java @@ -0,0 +1,49 @@ +/** + * 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.config.discovery.addon.candidate; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.jupnp.model.meta.RemoteDevice; + +/** + * This is a {@link UpnpCandidate} upnp version of class for candidates for + * suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class UpnpCandidate extends BaseCandidate { + + public UpnpCandidate(String bindingId, Map propertyMatchRegexMap) { + super(bindingId, propertyMatchRegexMap); + } + + /** + * Check if the data in the provided RemoteDevice matches our own match + * criteria. + */ + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof RemoteDevice device) { + return propertyMatches("deviceType", device.getType().getType()) + && propertyMatches("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) + && propertyMatches("model", device.getDetails().getModelDetails().getModelName()) + && propertyMatches("serialNumber", device.getDetails().getSerialNumber()) + && propertyMatches("udn", device.getIdentity().getUdn().getIdentifierString()); + } + return false; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java deleted file mode 100644 index 42443254bf5..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/MdnsAddonSuggestionParticipant.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 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.config.discovery.addon.discovery; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import javax.jmdns.ServiceInfo; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; -import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.service.component.annotations.Component; - -/** - * This is a {@link MdnsAddonSuggestionParticipant} mdns version of class for - * discovery participants to find suggested addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(service = MDNSDiscoveryParticipant.class, configurationPid = "discovery.mdns.suggestion.finder") -public class MdnsAddonSuggestionParticipant extends AddonSuggestionParticipant implements MDNSDiscoveryParticipant { - private final String serviceType; - - public MdnsAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegex, String serviceType) { - super(listener, bindingId, propertyMatchRegex); - this.serviceType = serviceType; - } - - @Override - public Set getSupportedThingTypeUIDs() { - return Set.of(); - } - - @Override - public String getServiceType() { - return serviceType; - } - - @Override - public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) { - if (isPropertyValid("application", serviceInfo.getApplication()) - && isPropertyValid("name", serviceInfo.getName()) - && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( - propertyName -> isPropertyValid(propertyName, serviceInfo.getPropertyString(propertyName)))) { - listener.onAddonSuggestionFound(bindingId); - } - return null; - } - - @Override - public @Nullable ThingUID getThingUID(ServiceInfo service) { - return null; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java deleted file mode 100644 index fb072da6b46..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/discovery/UpnpAddonSuggestionParticipant.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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.config.discovery.addon.discovery; - -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.jupnp.model.meta.RemoteDevice; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.addon.finder.AddonSuggestionListener; -import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.osgi.service.component.annotations.Component; - -/** - * This is a {@link UpnpAddonSuggestionParticipant} upnp version of class for - * discovery participants to find suggested addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(service = UpnpDiscoveryParticipant.class, configurationPid = "discovery.upnp.suggestion.finder") -public class UpnpAddonSuggestionParticipant extends AddonSuggestionParticipant implements UpnpDiscoveryParticipant { - - public UpnpAddonSuggestionParticipant(AddonSuggestionListener listener, String bindingId, - Map propertyMatchRegexMap) { - super(listener, bindingId, propertyMatchRegexMap); - } - - @Override - public Set getSupportedThingTypeUIDs() { - return Set.of(); - } - - @Override - public @Nullable DiscoveryResult createResult(RemoteDevice device) { - if (isPropertyValid("deviceType", device.getType().getType()) - && isPropertyValid("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) - && isPropertyValid("model", device.getDetails().getModelDetails().getModelName()) - && isPropertyValid("serialNumber", device.getDetails().getSerialNumber()) - && isPropertyValid("udn", device.getIdentity().getUdn().getIdentifierString())) { - listener.onAddonSuggestionFound(bindingId); - } - return null; - } - - @Override - public @Nullable ThingUID getThingUID(RemoteDevice device) { - return null; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java new file mode 100644 index 00000000000..a75ae62b3ef --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java @@ -0,0 +1,71 @@ +/** + * 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.config.discovery.addon.dto; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * DTO for serialization of a single addon suggestion candidate. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class Candidate { + private @NonNullByDefault({}) String addonId; + private @NonNullByDefault({}) String discoveryType; + private @NonNullByDefault({}) List properties; + private @Nullable String mdnsServiceType; + + public String getAddonId() { + return addonId; + } + + public DiscoveryType getDiscoveryType() { + return DiscoveryType.valueOf(discoveryType.toUpperCase()); + } + + public String getMdnsServiceType() { + String mdnsServiceType = this.mdnsServiceType; + return mdnsServiceType != null ? mdnsServiceType : ""; + } + + public Map getPropertyRegexMap() { + return properties.stream().collect(Collectors.toMap(x -> x.getName(), x -> x.getRegex())); + } + + public Candidate setAddonId(String addonId) { + this.addonId = addonId; + return this; + } + + public Candidate setDiscoveryType(DiscoveryType discoveryType) { + this.discoveryType = discoveryType.name().toLowerCase(); + return this; + } + + public Candidate setMdnsServiceType(String mdnsServiceType) { + this.mdnsServiceType = mdnsServiceType; + return this; + } + + public Candidate setPropertyRegexMap(Map propertyRegexMap) { + properties = propertyRegexMap.entrySet().stream().map(e -> new PropertyRegex(e.getKey(), e.getValue())) + .toList(); + return this; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java new file mode 100644 index 00000000000..4b15d5455a9 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java @@ -0,0 +1,36 @@ +/** + * 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.config.discovery.addon.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * DTO for serialization of addon suggestion candidates. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class Candidates { + private @NonNullByDefault({}) List candidates; + + public List getCandidates() { + return candidates; + } + + public Candidates setCandidates(List candidates) { + this.candidates = candidates; + return this; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java similarity index 66% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java index f452e20727e..7d93cad278d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionListener.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java @@ -10,17 +10,17 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.finder; +package org.openhab.core.config.discovery.addon.dto; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * This is a {@link AddonSuggestionListener} which is a callback for discovery of suggested addons. + * Enum of different supported discovery types. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public interface AddonSuggestionListener { - - public void onAddonSuggestionFound(String bindingId); +public enum DiscoveryType { + MDNS, + UPNP } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java new file mode 100644 index 00000000000..dd8299b288d --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java @@ -0,0 +1,39 @@ +/** + * 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.config.discovery.addon.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * DTO for serialization of a property match regular expression. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class PropertyRegex { + private @NonNullByDefault({}) String name; + private @NonNullByDefault({}) String regex; + + public PropertyRegex(String name, String regex) { + this.name = name; + this.regex = regex; + } + + public String getName() { + return name; + } + + public String getRegex() { + return regex; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index da017b4a650..bf01362365d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -13,47 +13,172 @@ package org.openhab.core.config.discovery.addon.finder; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; + +import javax.jmdns.ServiceEvent; +import javax.jmdns.ServiceInfo; +import javax.jmdns.ServiceListener; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.config.discovery.addon.discovery.AddonSuggestionParticipant; -import org.openhab.core.config.discovery.addon.discovery.MdnsAddonSuggestionParticipant; -import org.openhab.core.config.discovery.addon.discovery.UpnpAddonSuggestionParticipant; +import org.eclipse.jdt.annotation.Nullable; +import org.jupnp.UpnpService; +import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.config.discovery.addon.candidate.MdnsCandidate; +import org.openhab.core.config.discovery.addon.candidate.UpnpCandidate; +import org.openhab.core.config.discovery.addon.dto.Candidates; +import org.openhab.core.config.discovery.addon.xml.CandidatesSerializer; +import org.openhab.core.io.transport.mdns.MDNSClient; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This is a {@link AddonSuggestionFinder} which discovers suggested bindings to install. + * This is a {@link AddonSuggestionFinder} which discovers suggested addons for + * the user to install. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault @Component -public class AddonSuggestionFinder implements AddonSuggestionListener { +public class AddonSuggestionFinder implements AutoCloseable { - private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); + /** + * Inner ServiceListener implementation that ignores call-backs. + */ + private static class NoOp implements ServiceListener { + + @Override + public void serviceAdded(@Nullable ServiceEvent event) { + } - private final Set bindingIds = new HashSet<>(); - private final List addonSuggestionParticipants = new ArrayList<>(); + @Override + public void serviceRemoved(@Nullable ServiceEvent event) { + } - protected void initialize() { - AddonSuggestionParticipant mdnsParticipant = new MdnsAddonSuggestionParticipant(this, "aaa", Map.of(), "bbb"); - AddonSuggestionParticipant upnpParticipant = new UpnpAddonSuggestionParticipant(this, "ccc", Map.of()); - addonSuggestionParticipants.add(mdnsParticipant); - addonSuggestionParticipants.add(upnpParticipant); + @Override + public void serviceResolved(@Nullable ServiceEvent event) { + } } - protected void dispose() { - addonSuggestionParticipants.clear(); + private static final String FINDER_THREADPOOL_NAME = "addon-suggestion-finder"; + private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(FINDER_THREADPOOL_NAME); + private final NoOp noop = new NoOp(); + private final Set addonIds = ConcurrentHashMap.newKeySet(); + private final List mdnsCandidates = new ArrayList<>(); + private final List upnpCandidates = new ArrayList<>(); + private final CandidatesSerializer candidatesSerializer = new CandidatesSerializer(); + + private final MDNSClient mdnsClient; + private final UpnpService upnpService; + + @Activate + public AddonSuggestionFinder(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) { + this.mdnsClient = mdnsClient; + this.upnpService = upnpService; } @Override - public void onAddonSuggestionFound(String bindingId) { - logger.debug("found binding id:{}", bindingId); - bindingIds.add(bindingId); + public void close() throws Exception { + mdnsCandidates.forEach(candidate -> mdnsClient.removeServiceListener(candidate.getMdnsServiceType(), noop)); + mdnsCandidates.clear(); + upnpCandidates.clear(); + addonIds.clear(); + } + + /** + * Get the list of suggested addon ids. + */ + public List getSuggestions() { + return addonIds.stream().toList(); + } + + /** + * Initialize the AddonSuggestionFinder with XML data containing the list of potential addon candidates to be + * suggested. + * + * @param xml + */ + public void loadXML(String xml) { + try { + close(); + } catch (Exception e) { + // exception should not occur + } + Candidates candidates = candidatesSerializer.fromXML(xml); + candidates.getCandidates().forEach(c -> { + switch (c.getDiscoveryType()) { + case MDNS: + mdnsCandidates + .add(new MdnsCandidate(c.getAddonId(), c.getPropertyRegexMap(), c.getMdnsServiceType())); + break; + case UPNP: + upnpCandidates.add(new UpnpCandidate(c.getAddonId(), c.getPropertyRegexMap())); + break; + default: + break; + } + }); + mdnsCandidates.forEach(c -> mdnsClient.addServiceListener(c.getMdnsServiceType(), noop)); + } + + /** + * Run a scheduled task to search for matching addon suggestion candidates using + * the MDNS service. + */ + @SuppressWarnings("unlikely-arg-type") + private void startMdnsScan() { + scheduler.submit(() -> { + mdnsCandidates.forEach(candidate -> { + ServiceInfo[] services = mdnsClient.list(candidate.getMdnsServiceType()); + for (ServiceInfo service : services) { + if (candidate.equals(service)) { + suggestionFound(candidate.getAddonId()); + } + } + }); + }); + } + + /** + * Start the search process to find addons to suggest to be installed. + */ + public void startScan() { + startMdnsScan(); + startUpnpScan(); + } + + /** + * Run a scheduled task to search for matching addon suggestion candidates using + * the UPnP service. + */ + @SuppressWarnings("unlikely-arg-type") + private void startUpnpScan() { + scheduler.submit(() -> { + upnpService.getRegistry().getRemoteDevices().forEach(device -> { + upnpCandidates.forEach(candidate -> { + if (candidate.equals(device)) { + suggestionFound(candidate.getAddonId()); + } + }); + }); + }); + } + + /** + * Called back when a new addon suggestion is found. + * + * @param addonId + */ + private synchronized void suggestionFound(String addonId) { + if (!addonIds.contains(addonId)) { + logger.debug("found suggested addon id:{}", addonId); + addonIds.add(addonId); + } } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java new file mode 100644 index 00000000000..da39fd0ea84 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java @@ -0,0 +1,50 @@ +/** + * 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.config.discovery.addon.xml; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.discovery.addon.dto.Candidate; +import org.openhab.core.config.discovery.addon.dto.Candidates; +import org.openhab.core.config.discovery.addon.dto.PropertyRegex; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; + +/** + * Serializer/deserializer for addon suggestion finder. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class CandidatesSerializer { + + private final XStream xStream; + + public CandidatesSerializer() { + xStream = new XStream(new StaxDriver()); + xStream.alias("addon-suggestion", Candidates.class); + xStream.alias("candidate", Candidate.class); + xStream.alias("property", PropertyRegex.class); + xStream.addImplicitCollection(Candidates.class, "candidates"); + xStream.addImplicitCollection(Candidate.class, "properties"); + xStream.allowTypesByWildcard(new String[] { "org.openhab.**" }); + } + + public Candidates fromXML(String xml) { + return (Candidates) xStream.fromXML(xml); + } + + public String toXML(Candidates candidates) { + return xStream.toXML(candidates); + } +} From 8f8cab3bdf7530aeab0a0b290f52e860939d7baa Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 18 Sep 2023 17:23:30 +0100 Subject: [PATCH 05/98] [suggestion-finder] typo Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/xml/CandidatesSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java index da39fd0ea84..bcc59cf6c5a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java @@ -32,7 +32,7 @@ public class CandidatesSerializer { public CandidatesSerializer() { xStream = new XStream(new StaxDriver()); - xStream.alias("addon-suggestion", Candidates.class); + xStream.alias("addon-suggestions", Candidates.class); xStream.alias("candidate", Candidate.class); xStream.alias("property", PropertyRegex.class); xStream.addImplicitCollection(Candidates.class, "candidates"); From f4977286ecbea4d1bb8bd6d0c9a8e56c8a2ba121 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 18 Sep 2023 17:38:55 +0100 Subject: [PATCH 06/98] [suggestion-finder] refactoring Signed-off-by: Andrew Fiddian-Green --- .../addon/finder/AddonSuggestionFinder.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index bf01362365d..4cc08da3ed0 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -99,8 +99,8 @@ public List getSuggestions() { } /** - * Initialize the AddonSuggestionFinder with XML data containing the list of potential addon candidates to be - * suggested. + * Initialize the AddonSuggestionFinder with XML data containing the list of + * potential addon candidates to be suggested. * * @param xml */ @@ -111,20 +111,20 @@ public void loadXML(String xml) { // exception should not occur } Candidates candidates = candidatesSerializer.fromXML(xml); - candidates.getCandidates().forEach(c -> { - switch (c.getDiscoveryType()) { + candidates.getCandidates().forEach(candidate -> { + switch (candidate.getDiscoveryType()) { case MDNS: - mdnsCandidates - .add(new MdnsCandidate(c.getAddonId(), c.getPropertyRegexMap(), c.getMdnsServiceType())); + mdnsCandidates.add(new MdnsCandidate(candidate.getAddonId(), candidate.getPropertyRegexMap(), + candidate.getMdnsServiceType())); break; case UPNP: - upnpCandidates.add(new UpnpCandidate(c.getAddonId(), c.getPropertyRegexMap())); + upnpCandidates.add(new UpnpCandidate(candidate.getAddonId(), candidate.getPropertyRegexMap())); break; default: break; } }); - mdnsCandidates.forEach(c -> mdnsClient.addServiceListener(c.getMdnsServiceType(), noop)); + mdnsCandidates.forEach(candidate -> mdnsClient.addServiceListener(candidate.getMdnsServiceType(), noop)); } /** @@ -135,8 +135,7 @@ public void loadXML(String xml) { private void startMdnsScan() { scheduler.submit(() -> { mdnsCandidates.forEach(candidate -> { - ServiceInfo[] services = mdnsClient.list(candidate.getMdnsServiceType()); - for (ServiceInfo service : services) { + for (ServiceInfo service : mdnsClient.list(candidate.getMdnsServiceType())) { if (candidate.equals(service)) { suggestionFound(candidate.getAddonId()); } From db372885dd08eb26a3499a56d8b235a3d3569ac5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 18 Sep 2023 17:48:15 +0100 Subject: [PATCH 07/98] [suggestion-finder] fix modelName property Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/candidate/UpnpCandidate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java index 15c79b7a055..aa4b9433520 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java @@ -40,7 +40,7 @@ public boolean equals(@Nullable Object object) { if (object instanceof RemoteDevice device) { return propertyMatches("deviceType", device.getType().getType()) && propertyMatches("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) - && propertyMatches("model", device.getDetails().getModelDetails().getModelName()) + && propertyMatches("modelName", device.getDetails().getModelDetails().getModelName()) && propertyMatches("serialNumber", device.getDetails().getSerialNumber()) && propertyMatches("udn", device.getIdentity().getUdn().getIdentifierString()); } From 25952fdc3253bc24fb6baa68cdb83b70cc61ecca Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 18 Sep 2023 19:06:45 +0100 Subject: [PATCH 08/98] [suggestion-finder] add servlet, and javadocs Signed-off-by: Andrew Fiddian-Green --- .../addon/finder/AddonSuggestionFinder.java | 68 +++++++++++++++++-- .../addon/xml/CandidatesSerializer.java | 19 +++++- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index 4cc08da3ed0..3db7b55452f 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -12,6 +12,8 @@ */ package org.openhab.core.config.discovery.addon.finder; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -21,6 +23,12 @@ import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -34,9 +42,12 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.thoughtworks.xstream.XStreamException; + /** * This is a {@link AddonSuggestionFinder} which discovers suggested addons for * the user to install. @@ -44,8 +55,9 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component -public class AddonSuggestionFinder implements AutoCloseable { +@Component(immediate = true, service = Servlet.class) +@HttpWhiteboardServletName(AddonSuggestionFinder.SERVLET_PATH) +public class AddonSuggestionFinder extends HttpServlet implements AutoCloseable { /** * Inner ServiceListener implementation that ignores call-backs. @@ -65,7 +77,10 @@ public void serviceResolved(@Nullable ServiceEvent event) { } } + public static final String SERVLET_PATH = "/suggestions"; + private static final long serialVersionUID = -358506179462414301L; private static final String FINDER_THREADPOOL_NAME = "addon-suggestion-finder"; + private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(FINDER_THREADPOOL_NAME); private final NoOp noop = new NoOp(); @@ -91,6 +106,42 @@ public void close() throws Exception { addonIds.clear(); } + /** + * Process GET request by returning a comma separated list of suggested addon + * ids. + */ + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType(MediaType.TEXT_PLAIN); + resp.getWriter().write(String.join(",", getSuggestions())); + } + + /** + * Process POST requests containing an XML payload which should contain the + * suggestion candidates, load the XML payload, and finally start scanning for + * respective suggestions. + */ + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (req.getContentLength() <= 0) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no content"); + return; + } + if (!MediaType.TEXT_XML.equals(req.getContentType())) { + resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "content not xml"); + return; + } + try { + loadXML(new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); + resp.setStatus(HttpServletResponse.SC_OK); + startScan(); + } catch (XStreamException e) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "content invalid"); + throw new ServletException(e); + } + } + /** * Get the list of suggested addon ids. */ @@ -102,9 +153,10 @@ public List getSuggestions() { * Initialize the AddonSuggestionFinder with XML data containing the list of * potential addon candidates to be suggested. * - * @param xml + * @param xml an XML serial image. + * @throws XStreamException if the object cannot be deserialized. */ - public void loadXML(String xml) { + public void loadXML(String xml) throws XStreamException { try { close(); } catch (Exception e) { @@ -148,8 +200,12 @@ private void startMdnsScan() { * Start the search process to find addons to suggest to be installed. */ public void startScan() { - startMdnsScan(); - startUpnpScan(); + if (!mdnsCandidates.isEmpty()) { + startMdnsScan(); + } + if (!upnpCandidates.isEmpty()) { + startUpnpScan(); + } } /** diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java index bcc59cf6c5a..74371ed4d8a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java @@ -18,6 +18,7 @@ import org.openhab.core.config.discovery.addon.dto.PropertyRegex; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.io.xml.StaxDriver; /** @@ -40,11 +41,25 @@ public CandidatesSerializer() { xStream.allowTypesByWildcard(new String[] { "org.openhab.**" }); } - public Candidates fromXML(String xml) { + /** + * Deserialize the XML into a Candidates DTO. + * + * @param xml an XML serial image. + * @return a deserialized Candidates DTO. + * @throws XStreamException if unable to deserialize the XML. + */ + public Candidates fromXML(String xml) throws XStreamException { return (Candidates) xStream.fromXML(xml); } - public String toXML(Candidates candidates) { + /** + * Serialize a Candidates DTO to XML. + * + * @param candidates the DTO to be serialized. + * @return an XML serial image of the DTO. + * @throws XStreamException if unable to serialize the DTO. + */ + public String toXML(Candidates candidates) throws XStreamException { return xStream.toXML(candidates); } } From 69bfdb37d66f0310b57700211a0710f05e7cd6a3 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 15:01:13 +0100 Subject: [PATCH 09/98] [suggestion-finder] move servlet to rest api; refactoring Signed-off-by: Andrew Fiddian-Green --- .../addon/finder/AddonSuggestionFinder.java | 78 +++---------------- bundles/org.openhab.core.io.rest.core/pom.xml | 5 ++ .../core/internal/addons/AddonResource.java | 21 ++++- 3 files changed, 34 insertions(+), 70 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index 3db7b55452f..ee314d69d3d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -12,23 +12,15 @@ */ package org.openhab.core.config.discovery.addon.finder; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import javax.jmdns.ServiceEvent; -import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; -import javax.servlet.Servlet; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.MediaType; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -42,7 +34,6 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,9 +46,8 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(immediate = true, service = Servlet.class) -@HttpWhiteboardServletName(AddonSuggestionFinder.SERVLET_PATH) -public class AddonSuggestionFinder extends HttpServlet implements AutoCloseable { +@Component(immediate = true) +public class AddonSuggestionFinder implements AutoCloseable { /** * Inner ServiceListener implementation that ignores call-backs. @@ -77,8 +67,6 @@ public void serviceResolved(@Nullable ServiceEvent event) { } } - public static final String SERVLET_PATH = "/suggestions"; - private static final long serialVersionUID = -358506179462414301L; private static final String FINDER_THREADPOOL_NAME = "addon-suggestion-finder"; private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); @@ -106,42 +94,6 @@ public void close() throws Exception { addonIds.clear(); } - /** - * Process GET request by returning a comma separated list of suggested addon - * ids. - */ - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.setStatus(HttpServletResponse.SC_OK); - resp.setContentType(MediaType.TEXT_PLAIN); - resp.getWriter().write(String.join(",", getSuggestions())); - } - - /** - * Process POST requests containing an XML payload which should contain the - * suggestion candidates, load the XML payload, and finally start scanning for - * respective suggestions. - */ - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - if (req.getContentLength() <= 0) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no content"); - return; - } - if (!MediaType.TEXT_XML.equals(req.getContentType())) { - resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "content not xml"); - return; - } - try { - loadXML(new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8)); - resp.setStatus(HttpServletResponse.SC_OK); - startScan(); - } catch (XStreamException e) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "content invalid"); - throw new ServletException(e); - } - } - /** * Get the list of suggested addon ids. */ @@ -185,15 +137,9 @@ public void loadXML(String xml) throws XStreamException { */ @SuppressWarnings("unlikely-arg-type") private void startMdnsScan() { - scheduler.submit(() -> { - mdnsCandidates.forEach(candidate -> { - for (ServiceInfo service : mdnsClient.list(candidate.getMdnsServiceType())) { - if (candidate.equals(service)) { - suggestionFound(candidate.getAddonId()); - } - } - }); - }); + scheduler.submit(() -> mdnsCandidates.forEach(candidate -> Arrays + .stream(mdnsClient.list(candidate.getMdnsServiceType())).filter(service -> candidate.equals(service)) + .forEach(service -> suggestionFound(candidate.getAddonId())))); } /** @@ -214,15 +160,9 @@ public void startScan() { */ @SuppressWarnings("unlikely-arg-type") private void startUpnpScan() { - scheduler.submit(() -> { - upnpService.getRegistry().getRemoteDevices().forEach(device -> { - upnpCandidates.forEach(candidate -> { - if (candidate.equals(device)) { - suggestionFound(candidate.getAddonId()); - } - }); - }); - }); + scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices() + .forEach(device -> upnpCandidates.stream().filter(candidate -> candidate.equals(device)) + .forEach(candidate -> suggestionFound(candidate.getAddonId())))); } /** diff --git a/bundles/org.openhab.core.io.rest.core/pom.xml b/bundles/org.openhab.core.io.rest.core/pom.xml index e829c11cd58..20f2604bbf4 100644 --- a/bundles/org.openhab.core.io.rest.core/pom.xml +++ b/bundles/org.openhab.core.io.rest.core/pom.xml @@ -35,6 +35,11 @@ org.openhab.core.config.discovery ${project.version} + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + org.openhab.core.bundles org.openhab.core.io.rest diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index b2b4f8beebf..40d209ad039 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -56,6 +56,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.config.discovery.addon.finder.AddonSuggestionFinder; import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.JSONResponse; @@ -120,6 +121,7 @@ public class AddonResource implements RESTResource { private final ConfigurationService configurationService; private final AddonInfoRegistry addonInfoRegistry; private final ConfigDescriptionRegistry configDescriptionRegistry; + private final AddonSuggestionFinder addonSuggestionFinder; private @Context @NonNullByDefault({}) UriInfo uriInfo; @@ -127,12 +129,14 @@ public class AddonResource implements RESTResource { public AddonResource(final @Reference EventPublisher eventPublisher, final @Reference LocaleService localeService, final @Reference ConfigurationService configurationService, final @Reference AddonInfoRegistry addonInfoRegistry, - final @Reference ConfigDescriptionRegistry configDescriptionRegistry) { + final @Reference ConfigDescriptionRegistry configDescriptionRegistry, + final @Reference AddonSuggestionFinder addonSuggestionFinder) { this.eventPublisher = eventPublisher; this.localeService = localeService; this.configurationService = configurationService; this.addonInfoRegistry = addonInfoRegistry; this.configDescriptionRegistry = configDescriptionRegistry; + this.addonSuggestionFinder = addonSuggestionFinder; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -178,6 +182,21 @@ public Response getServices( return Response.ok(new Stream2JSONInputStream(addonTypeStream)).build(); } + @GET + @Path("/suggestions") + @Produces(MediaType.APPLICATION_JSON) + @Operation(operationId = "getSuggestedAddons", summary = "Get suggested add-ons to be installed.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Addon.class)))), }) + public Response getSuggestions( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { + logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); + Locale locale = localeService.getLocale(language); + List suggestions = addonSuggestionFinder.getSuggestions(); + return Response.ok( + new Stream2JSONInputStream(getAllAddons(locale).filter(addon -> suggestions.contains(addon.getUid())))) + .build(); + } + @GET @Path("/types") @Produces(MediaType.APPLICATION_JSON) From 3c1f6a2adb9ff4c525d9e62652ff9a9f2255de29 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 15:20:47 +0100 Subject: [PATCH 10/98] [suggestion-finder] refactoring Signed-off-by: Andrew Fiddian-Green --- .../config/discovery/addon/dto/Candidate.java | 10 ++++---- .../addon/finder/AddonSuggestionFinder.java | 24 +++++++++---------- .../core/internal/addons/AddonResource.java | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java index a75ae62b3ef..39a4d598e3e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java @@ -26,13 +26,13 @@ */ @NonNullByDefault public class Candidate { - private @NonNullByDefault({}) String addonId; + private @NonNullByDefault({}) String addonUid; private @NonNullByDefault({}) String discoveryType; private @NonNullByDefault({}) List properties; private @Nullable String mdnsServiceType; - public String getAddonId() { - return addonId; + public String getAddonUid() { + return addonUid; } public DiscoveryType getDiscoveryType() { @@ -48,8 +48,8 @@ public Map getPropertyRegexMap() { return properties.stream().collect(Collectors.toMap(x -> x.getName(), x -> x.getRegex())); } - public Candidate setAddonId(String addonId) { - this.addonId = addonId; + public Candidate setAddonUid(String addonUid) { + this.addonUid = addonUid; return this; } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index ee314d69d3d..858e20ac2d7 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -72,7 +72,7 @@ public void serviceResolved(@Nullable ServiceEvent event) { private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(FINDER_THREADPOOL_NAME); private final NoOp noop = new NoOp(); - private final Set addonIds = ConcurrentHashMap.newKeySet(); + private final Set suggestedAddonUids = ConcurrentHashMap.newKeySet(); private final List mdnsCandidates = new ArrayList<>(); private final List upnpCandidates = new ArrayList<>(); private final CandidatesSerializer candidatesSerializer = new CandidatesSerializer(); @@ -91,14 +91,14 @@ public void close() throws Exception { mdnsCandidates.forEach(candidate -> mdnsClient.removeServiceListener(candidate.getMdnsServiceType(), noop)); mdnsCandidates.clear(); upnpCandidates.clear(); - addonIds.clear(); + suggestedAddonUids.clear(); } /** - * Get the list of suggested addon ids. + * Get the list of suggested addon Uids. */ - public List getSuggestions() { - return addonIds.stream().toList(); + public List getSuggestedAddonUids() { + return suggestedAddonUids.stream().toList(); } /** @@ -118,11 +118,11 @@ public void loadXML(String xml) throws XStreamException { candidates.getCandidates().forEach(candidate -> { switch (candidate.getDiscoveryType()) { case MDNS: - mdnsCandidates.add(new MdnsCandidate(candidate.getAddonId(), candidate.getPropertyRegexMap(), + mdnsCandidates.add(new MdnsCandidate(candidate.getAddonUid(), candidate.getPropertyRegexMap(), candidate.getMdnsServiceType())); break; case UPNP: - upnpCandidates.add(new UpnpCandidate(candidate.getAddonId(), candidate.getPropertyRegexMap())); + upnpCandidates.add(new UpnpCandidate(candidate.getAddonUid(), candidate.getPropertyRegexMap())); break; default: break; @@ -168,12 +168,12 @@ private void startUpnpScan() { /** * Called back when a new addon suggestion is found. * - * @param addonId + * @param addonUid the Uid of the found addon. */ - private synchronized void suggestionFound(String addonId) { - if (!addonIds.contains(addonId)) { - logger.debug("found suggested addon id:{}", addonId); - addonIds.add(addonId); + private synchronized void suggestionFound(String addonUid) { + if (!suggestedAddonUids.contains(addonUid)) { + logger.debug("found suggested addon id:{}", addonUid); + suggestedAddonUids.add(addonUid); } } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 40d209ad039..fe6c41d61ce 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -191,9 +191,9 @@ public Response getSuggestions( @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); - List suggestions = addonSuggestionFinder.getSuggestions(); + List suggestedAddonUids = addonSuggestionFinder.getSuggestedAddonUids(); return Response.ok( - new Stream2JSONInputStream(getAllAddons(locale).filter(addon -> suggestions.contains(addon.getUid())))) + new Stream2JSONInputStream(getAllAddons(locale).filter(addon -> suggestedAddonUids.contains(addon.getUid())))) .build(); } From 4d63b5d097a32143e20967aa5fcf2e1e94bf2c1f Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 15:33:20 +0100 Subject: [PATCH 11/98] [suggestion-finder] spotless Signed-off-by: Andrew Fiddian-Green --- .../core/io/rest/core/internal/addons/AddonResource.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index fe6c41d61ce..2a11566b041 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -192,9 +192,8 @@ public Response getSuggestions( logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); List suggestedAddonUids = addonSuggestionFinder.getSuggestedAddonUids(); - return Response.ok( - new Stream2JSONInputStream(getAllAddons(locale).filter(addon -> suggestedAddonUids.contains(addon.getUid())))) - .build(); + return Response.ok(new Stream2JSONInputStream( + getAllAddons(locale).filter(addon -> suggestedAddonUids.contains(addon.getUid())))).build(); } @GET From bb6261aa984ec1c268d4a62c35982dc89d97b11f Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 18:50:03 +0100 Subject: [PATCH 12/98] [suggestion-finder] spotless Signed-off-by: Andrew Fiddian-Green --- .../config/discovery/addon/dto/Candidate.java | 46 ++++--------- .../discovery/addon/dto/DiscoveryMethod.java | 64 +++++++++++++++++++ ...{PropertyRegex.java => MatchProperty.java} | 15 +++-- .../{DiscoveryType.java => ServiceType.java} | 2 +- ...tes.java => SuggestedAddonCandidates.java} | 12 ++-- .../addon/finder/AddonSuggestionFinder.java | 36 ++++++----- ...er.java => AddonCandidatesSerializer.java} | 38 +++++++---- 7 files changed, 136 insertions(+), 77 deletions(-) create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/{PropertyRegex.java => MatchProperty.java} (68%) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/{DiscoveryType.java => ServiceType.java} (95%) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/{Candidates.java => SuggestedAddonCandidates.java} (63%) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/{CandidatesSerializer.java => AddonCandidatesSerializer.java} (51%) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java index 39a4d598e3e..bd38e68ba8c 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java @@ -13,59 +13,37 @@ package org.openhab.core.config.discovery.addon.dto; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** - * DTO for serialization of a single addon suggestion candidate. + * DTO for serialization of a single addon suggestion discovery candidate. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault public class Candidate { - private @NonNullByDefault({}) String addonUid; - private @NonNullByDefault({}) String discoveryType; - private @NonNullByDefault({}) List properties; - private @Nullable String mdnsServiceType; + private @Nullable String addonUid; + private @Nullable List discoveryMethods; - public String getAddonUid() { - return addonUid; - } - - public DiscoveryType getDiscoveryType() { - return DiscoveryType.valueOf(discoveryType.toUpperCase()); + public List getDiscoveryMethods() { + List discoveryMethods = this.discoveryMethods; + return discoveryMethods != null ? discoveryMethods : List.of(); } - public String getMdnsServiceType() { - String mdnsServiceType = this.mdnsServiceType; - return mdnsServiceType != null ? mdnsServiceType : ""; + public String getAddonUid() { + String addonUid = this.addonUid; + return addonUid != null ? addonUid : ""; } - public Map getPropertyRegexMap() { - return properties.stream().collect(Collectors.toMap(x -> x.getName(), x -> x.getRegex())); + public Candidate setDiscoveryMethods(List discoveryMethods) { + this.discoveryMethods = discoveryMethods; + return this; } public Candidate setAddonUid(String addonUid) { this.addonUid = addonUid; return this; } - - public Candidate setDiscoveryType(DiscoveryType discoveryType) { - this.discoveryType = discoveryType.name().toLowerCase(); - return this; - } - - public Candidate setMdnsServiceType(String mdnsServiceType) { - this.mdnsServiceType = mdnsServiceType; - return this; - } - - public Candidate setPropertyRegexMap(Map propertyRegexMap) { - properties = propertyRegexMap.entrySet().stream().map(e -> new PropertyRegex(e.getKey(), e.getValue())) - .toList(); - return this; - } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java new file mode 100644 index 00000000000..9109718a427 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java @@ -0,0 +1,64 @@ +/** + * 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.config.discovery.addon.dto; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * DTO for serialization of a single addon suggestion candidate discovery method. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class DiscoveryMethod { + private @Nullable String serviceType; + private @Nullable List matchProperties; + private @Nullable String mdnsServiceType; + + public ServiceType getServiceType() { + String serviceType = this.serviceType; + return ServiceType.valueOf(serviceType != null ? serviceType.toUpperCase() : ""); + } + + public String getMdnsServiceType() { + String mdnsServiceType = this.mdnsServiceType; + return mdnsServiceType != null ? mdnsServiceType : ""; + } + + public Map getMatchProperties() { + return matchProperties != null + ? matchProperties.stream().collect(Collectors.toMap(x -> x.getName(), x -> x.getRegex())) + : Map.of(); + } + + public DiscoveryMethod setServiceType(ServiceType serviceType) { + this.serviceType = serviceType.name().toLowerCase(); + return this; + } + + public DiscoveryMethod setMdnsServiceType(String mdnsServiceType) { + this.mdnsServiceType = mdnsServiceType; + return this; + } + + public DiscoveryMethod setMatchProperties(Map matchProperties) { + this.matchProperties = matchProperties.entrySet().stream().map(e -> new MatchProperty(e.getKey(), e.getValue())) + .toList(); + return this; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java similarity index 68% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java index dd8299b288d..5bd294fce1b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/PropertyRegex.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java @@ -13,6 +13,7 @@ package org.openhab.core.config.discovery.addon.dto; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * DTO for serialization of a property match regular expression. @@ -20,20 +21,22 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class PropertyRegex { - private @NonNullByDefault({}) String name; - private @NonNullByDefault({}) String regex; +public class MatchProperty { + private @Nullable String name; + private @Nullable String regex; - public PropertyRegex(String name, String regex) { + public MatchProperty(String name, String regex) { this.name = name; this.regex = regex; } public String getName() { - return name; + String name = this.name; + return name != null ? name : ""; } public String getRegex() { - return regex; + String regex = this.regex; + return regex != null ? regex : ""; } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java similarity index 95% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java index 7d93cad278d..23ad110de24 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryType.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java @@ -20,7 +20,7 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public enum DiscoveryType { +public enum ServiceType { MDNS, UPNP } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java similarity index 63% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java index 4b15d5455a9..c374f000cf6 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidates.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java @@ -15,21 +15,23 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** - * DTO for serialization of addon suggestion candidates. + * DTO for serialization of a collection of potential suggested addon candidates. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class Candidates { - private @NonNullByDefault({}) List candidates; +public class SuggestedAddonCandidates { + private @Nullable List candidates; public List getCandidates() { - return candidates; + List candidates = this.candidates; + return candidates != null ? candidates : List.of(); } - public Candidates setCandidates(List candidates) { + public SuggestedAddonCandidates setCandidates(List candidates) { this.candidates = candidates; return this; } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java index 858e20ac2d7..72b8ed8a19a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java @@ -28,8 +28,8 @@ import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.discovery.addon.candidate.MdnsCandidate; import org.openhab.core.config.discovery.addon.candidate.UpnpCandidate; -import org.openhab.core.config.discovery.addon.dto.Candidates; -import org.openhab.core.config.discovery.addon.xml.CandidatesSerializer; +import org.openhab.core.config.discovery.addon.dto.SuggestedAddonCandidates; +import org.openhab.core.config.discovery.addon.xml.AddonCandidatesSerializer; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -75,7 +75,7 @@ public void serviceResolved(@Nullable ServiceEvent event) { private final Set suggestedAddonUids = ConcurrentHashMap.newKeySet(); private final List mdnsCandidates = new ArrayList<>(); private final List upnpCandidates = new ArrayList<>(); - private final CandidatesSerializer candidatesSerializer = new CandidatesSerializer(); + private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); private final MDNSClient mdnsClient; private final UpnpService upnpService; @@ -114,20 +114,22 @@ public void loadXML(String xml) throws XStreamException { } catch (Exception e) { // exception should not occur } - Candidates candidates = candidatesSerializer.fromXML(xml); - candidates.getCandidates().forEach(candidate -> { - switch (candidate.getDiscoveryType()) { - case MDNS: - mdnsCandidates.add(new MdnsCandidate(candidate.getAddonUid(), candidate.getPropertyRegexMap(), - candidate.getMdnsServiceType())); - break; - case UPNP: - upnpCandidates.add(new UpnpCandidate(candidate.getAddonUid(), candidate.getPropertyRegexMap())); - break; - default: - break; - } - }); + SuggestedAddonCandidates candidates = addonCandidatesSerializer.fromXML(xml); + candidates.getCandidates().stream() + .forEach(candidate -> candidate.getDiscoveryMethods().stream().forEach(discoveryMethod -> { + switch (discoveryMethod.getServiceType()) { + case MDNS: + mdnsCandidates.add(new MdnsCandidate(candidate.getAddonUid(), + discoveryMethod.getMatchProperties(), discoveryMethod.getMdnsServiceType())); + break; + case UPNP: + upnpCandidates.add( + new UpnpCandidate(candidate.getAddonUid(), discoveryMethod.getMatchProperties())); + break; + default: + break; + } + })); mdnsCandidates.forEach(candidate -> mdnsClient.addServiceListener(candidate.getMdnsServiceType(), noop)); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java similarity index 51% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java index 74371ed4d8a..7861638085b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/CandidatesSerializer.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java @@ -14,52 +14,62 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.config.discovery.addon.dto.Candidate; -import org.openhab.core.config.discovery.addon.dto.Candidates; -import org.openhab.core.config.discovery.addon.dto.PropertyRegex; +import org.openhab.core.config.discovery.addon.dto.DiscoveryMethod; +import org.openhab.core.config.discovery.addon.dto.MatchProperty; +import org.openhab.core.config.discovery.addon.dto.SuggestedAddonCandidates; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.io.xml.StaxDriver; /** - * Serializer/deserializer for addon suggestion finder. + * Serializer/deserializer for suggested addon finder. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public class CandidatesSerializer { +public class AddonCandidatesSerializer { private final XStream xStream; - public CandidatesSerializer() { + public AddonCandidatesSerializer() { xStream = new XStream(new StaxDriver()); - xStream.alias("addon-suggestions", Candidates.class); + xStream.alias("suggested-addon-candidates", SuggestedAddonCandidates.class); + xStream.addImplicitCollection(SuggestedAddonCandidates.class, "candidates"); + xStream.alias("candidate", Candidate.class); - xStream.alias("property", PropertyRegex.class); - xStream.addImplicitCollection(Candidates.class, "candidates"); - xStream.addImplicitCollection(Candidate.class, "properties"); + xStream.aliasField("addon-uid", Candidate.class, "addonUid"); + xStream.addImplicitCollection(Candidate.class, "discoveryMethods"); + + xStream.alias("discovery-method", DiscoveryMethod.class); + xStream.aliasField("service-type", DiscoveryMethod.class, "serviceType"); + xStream.aliasField("mdns-service-type", DiscoveryMethod.class, "mdnsServiceType"); + xStream.aliasField("match-properties", DiscoveryMethod.class, "matchProperties"); + xStream.addImplicitCollection(DiscoveryMethod.class, "matchProperties"); + + xStream.alias("match-property", MatchProperty.class); xStream.allowTypesByWildcard(new String[] { "org.openhab.**" }); } /** * Deserialize the XML into a Candidates DTO. - * + * * @param xml an XML serial image. * @return a deserialized Candidates DTO. * @throws XStreamException if unable to deserialize the XML. */ - public Candidates fromXML(String xml) throws XStreamException { - return (Candidates) xStream.fromXML(xml); + public SuggestedAddonCandidates fromXML(String xml) throws XStreamException { + return (SuggestedAddonCandidates) xStream.fromXML(xml); } /** * Serialize a Candidates DTO to XML. - * + * * @param candidates the DTO to be serialized. * @return an XML serial image of the DTO. * @throws XStreamException if unable to serialize the DTO. */ - public String toXML(Candidates candidates) throws XStreamException { + public String toXML(SuggestedAddonCandidates candidates) throws XStreamException { return xStream.toXML(candidates); } } From 75bb0f44b990a116ede43b2bff1445229d2fad10 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 20:32:13 +0100 Subject: [PATCH 13/98] [suggestion-finder] implement AddonService Signed-off-by: Andrew Fiddian-Green --- .../pom.xml | 5 + ...java => AddonSuggestionFinderService.java} | 148 ++++++++++++++---- .../core/internal/addons/AddonResource.java | 12 +- 3 files changed, 124 insertions(+), 41 deletions(-) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/{AddonSuggestionFinder.java => AddonSuggestionFinderService.java} (51%) diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml index 797d8751d9a..fefb7005ada 100644 --- a/bundles/org.openhab.core.config.discovery.addon/pom.xml +++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml @@ -25,6 +25,11 @@ org.openhab.core.config.discovery.upnp ${project.version} + + org.openhab.core.bundles + org.openhab.core.addon + ${project.version} + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java similarity index 51% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java index 72b8ed8a19a..2ebb72aaadc 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java @@ -12,11 +12,16 @@ */ package org.openhab.core.config.discovery.addon.finder; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import javax.jmdns.ServiceEvent; @@ -25,6 +30,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.jupnp.UpnpService; +import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonService; +import org.openhab.core.addon.AddonType; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.discovery.addon.candidate.MdnsCandidate; import org.openhab.core.config.discovery.addon.candidate.UpnpCandidate; @@ -34,20 +42,22 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.XStreamException; /** - * This is a {@link AddonSuggestionFinder} which discovers suggested addons for - * the user to install. + * This is a {@link AddonSuggestionFinderService} which discovers suggested + * addons for the user to install. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(immediate = true) -public class AddonSuggestionFinder implements AutoCloseable { +@Component(immediate = true, service = AddonService.class, name = AddonSuggestionFinderService.SERVICE_NAME) +public class AddonSuggestionFinderService implements AddonService, AutoCloseable { /** * Inner ServiceListener implementation that ignores call-backs. @@ -67,38 +77,98 @@ public void serviceResolved(@Nullable ServiceEvent event) { } } + public static final String SERVICE_ID = "suggestions"; + public static final String SERVICE_NAME = "Suggested addon finder service"; + private static final String FINDER_THREADPOOL_NAME = "addon-suggestion-finder"; - private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinder.class); + private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinderService.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(FINDER_THREADPOOL_NAME); private final NoOp noop = new NoOp(); private final Set suggestedAddonUids = ConcurrentHashMap.newKeySet(); private final List mdnsCandidates = new ArrayList<>(); private final List upnpCandidates = new ArrayList<>(); private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); + private final Set addonServices = new CopyOnWriteArraySet<>(); private final MDNSClient mdnsClient; private final UpnpService upnpService; + private @Nullable Future mdnsScanTask; + private @Nullable Future upnpScanTask; + @Activate - public AddonSuggestionFinder(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) { + public AddonSuggestionFinderService(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) { this.mdnsClient = mdnsClient; this.upnpService = upnpService; } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addAddonService(AddonService addonService) { + // exclude ourself from addonServices in order to prevent recursion + if (!SERVICE_ID.equals(addonService.getId())) { + addonServices.add(addonService); + } + } + + protected void removeAddonService(AddonService addonService) { + addonServices.remove(addonService); + } + @Override public void close() throws Exception { - mdnsCandidates.forEach(candidate -> mdnsClient.removeServiceListener(candidate.getMdnsServiceType(), noop)); + mdnsCandidates.forEach(c -> mdnsClient.removeServiceListener(c.getMdnsServiceType(), noop)); mdnsCandidates.clear(); upnpCandidates.clear(); suggestedAddonUids.clear(); } - /** - * Get the list of suggested addon Uids. - */ - public List getSuggestedAddonUids() { - return suggestedAddonUids.stream().toList(); + @Override + public @Nullable Addon getAddon(String id, @Nullable Locale locale) { + return null; + } + + @Override + public @Nullable String getAddonId(URI addonURI) { + return null; + } + + @Override + public List getAddons(@Nullable Locale locale) { + return addonServices.stream().filter(s -> !this.getId().equals(s.getId())).map(s -> s.getAddons(locale)) + .flatMap(Collection::stream).filter(a -> suggestedAddonUids.contains(a.getUid())).toList(); + } + + @Override + public String getId() { + return SERVICE_ID; + } + + @Override + public String getName() { + return SERVICE_NAME; + } + + @Override + public List getTypes(@Nullable Locale locale) { + return List.of(AddonType.BINDING); + } + + @Override + public void install(String id) { + startScan(); + } + + @Override + public void uninstall(String id) { + Future task = upnpScanTask; + if (task != null) { + task.cancel(true); + } + task = mdnsScanTask; + if (task != null) { + task.cancel(true); + } } /** @@ -115,22 +185,25 @@ public void loadXML(String xml) throws XStreamException { // exception should not occur } SuggestedAddonCandidates candidates = addonCandidatesSerializer.fromXML(xml); - candidates.getCandidates().stream() - .forEach(candidate -> candidate.getDiscoveryMethods().stream().forEach(discoveryMethod -> { - switch (discoveryMethod.getServiceType()) { - case MDNS: - mdnsCandidates.add(new MdnsCandidate(candidate.getAddonUid(), - discoveryMethod.getMatchProperties(), discoveryMethod.getMdnsServiceType())); - break; - case UPNP: - upnpCandidates.add( - new UpnpCandidate(candidate.getAddonUid(), discoveryMethod.getMatchProperties())); - break; - default: - break; - } - })); - mdnsCandidates.forEach(candidate -> mdnsClient.addServiceListener(candidate.getMdnsServiceType(), noop)); + candidates.getCandidates().stream().forEach(c -> c.getDiscoveryMethods().stream().forEach(m -> { + switch (m.getServiceType()) { + case MDNS: + mdnsCandidates + .add(new MdnsCandidate(c.getAddonUid(), m.getMatchProperties(), m.getMdnsServiceType())); + break; + case UPNP: + upnpCandidates.add(new UpnpCandidate(c.getAddonUid(), m.getMatchProperties())); + break; + default: + break; + } + })); + mdnsCandidates.forEach(c -> mdnsClient.addServiceListener(c.getMdnsServiceType(), noop)); + } + + @Override + public void refreshSource() { + startScan(); } /** @@ -139,9 +212,13 @@ public void loadXML(String xml) throws XStreamException { */ @SuppressWarnings("unlikely-arg-type") private void startMdnsScan() { - scheduler.submit(() -> mdnsCandidates.forEach(candidate -> Arrays - .stream(mdnsClient.list(candidate.getMdnsServiceType())).filter(service -> candidate.equals(service)) - .forEach(service -> suggestionFound(candidate.getAddonId())))); + Future task = mdnsScanTask; + if (task != null) { + task.cancel(false); + } + mdnsScanTask = scheduler + .submit(() -> mdnsCandidates.forEach(c -> Arrays.stream(mdnsClient.list(c.getMdnsServiceType())) + .filter(s -> c.equals(s)).forEach(s -> suggestionFound(c.getAddonId())))); } /** @@ -162,9 +239,12 @@ public void startScan() { */ @SuppressWarnings("unlikely-arg-type") private void startUpnpScan() { - scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices() - .forEach(device -> upnpCandidates.stream().filter(candidate -> candidate.equals(device)) - .forEach(candidate -> suggestionFound(candidate.getAddonId())))); + Future task = upnpScanTask; + if (task != null) { + task.cancel(false); + } + upnpScanTask = scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices().forEach( + d -> upnpCandidates.stream().filter(c -> c.equals(d)).forEach(c -> suggestionFound(c.getAddonId())))); } /** diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 2a11566b041..237465165d2 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -56,7 +56,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.config.discovery.addon.finder.AddonSuggestionFinder; +import org.openhab.core.config.discovery.addon.finder.AddonSuggestionFinderService; import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.JSONResponse; @@ -121,7 +121,7 @@ public class AddonResource implements RESTResource { private final ConfigurationService configurationService; private final AddonInfoRegistry addonInfoRegistry; private final ConfigDescriptionRegistry configDescriptionRegistry; - private final AddonSuggestionFinder addonSuggestionFinder; + private final AddonSuggestionFinderService addonSuggestionFinderService; private @Context @NonNullByDefault({}) UriInfo uriInfo; @@ -130,13 +130,13 @@ public AddonResource(final @Reference EventPublisher eventPublisher, final @Refe final @Reference ConfigurationService configurationService, final @Reference AddonInfoRegistry addonInfoRegistry, final @Reference ConfigDescriptionRegistry configDescriptionRegistry, - final @Reference AddonSuggestionFinder addonSuggestionFinder) { + final @Reference AddonSuggestionFinderService addonSuggestionFinderService) { this.eventPublisher = eventPublisher; this.localeService = localeService; this.configurationService = configurationService; this.addonInfoRegistry = addonInfoRegistry; this.configDescriptionRegistry = configDescriptionRegistry; - this.addonSuggestionFinder = addonSuggestionFinder; + this.addonSuggestionFinderService = addonSuggestionFinderService; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -191,9 +191,7 @@ public Response getSuggestions( @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); - List suggestedAddonUids = addonSuggestionFinder.getSuggestedAddonUids(); - return Response.ok(new Stream2JSONInputStream( - getAllAddons(locale).filter(addon -> suggestedAddonUids.contains(addon.getUid())))).build(); + return Response.ok(new Stream2JSONInputStream(addonSuggestionFinderService.getAddons(locale).stream())).build(); } @GET From 9bfcb0dcdd668ca9df27e9efe79045cef664d2ff Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 19 Sep 2023 23:31:33 +0100 Subject: [PATCH 14/98] [suggestion-finder] tweaks Signed-off-by: Andrew Fiddian-Green --- .../addon/finder/AddonSuggestionFinderService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java index 2ebb72aaadc..c190e9f340a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java @@ -41,6 +41,7 @@ import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; @@ -105,7 +106,7 @@ public AddonSuggestionFinderService(@Reference MDNSClient mdnsClient, @Reference @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) protected void addAddonService(AddonService addonService) { - // exclude ourself from addonServices in order to prevent recursion + // exclude ourself from addonServices set in order to prevent recursion if (!SERVICE_ID.equals(addonService.getId())) { addonServices.add(addonService); } @@ -115,6 +116,7 @@ protected void removeAddonService(AddonService addonService) { addonServices.remove(addonService); } + @Deactivate @Override public void close() throws Exception { mdnsCandidates.forEach(c -> mdnsClient.removeServiceListener(c.getMdnsServiceType(), noop)); @@ -125,7 +127,9 @@ public void close() throws Exception { @Override public @Nullable Addon getAddon(String id, @Nullable Locale locale) { - return null; + return addonServices.stream().filter(s -> !SERVICE_ID.equals(s.getId())).map(s -> s.getAddons(locale)) + .flatMap(Collection::stream).filter(a -> suggestedAddonUids.contains(a.getUid())) + .filter(a -> id.equals(a.getId())).findAny().orElse(null); } @Override @@ -135,7 +139,7 @@ public void close() throws Exception { @Override public List getAddons(@Nullable Locale locale) { - return addonServices.stream().filter(s -> !this.getId().equals(s.getId())).map(s -> s.getAddons(locale)) + return addonServices.stream().filter(s -> !SERVICE_ID.equals(s.getId())).map(s -> s.getAddons(locale)) .flatMap(Collection::stream).filter(a -> suggestedAddonUids.contains(a.getUid())).toList(); } From 9132a4e41dd2f579175dd5f4de80a6f4324df029 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 20 Sep 2023 23:12:39 +0100 Subject: [PATCH 15/98] [suggestion-finder] schema; xml resource; itests Signed-off-by: Andrew Fiddian-Green --- .../suggested-addon-candidates-1.0.0.xsd | 39 ++++ .../pom.xml | 10 +- .../discovery/addon/dto/DiscoveryMethod.java | 6 +- .../finder/AddonSuggestionFinderService.java | 139 +++++++------ .../resources/suggested-addon-candidates.xml | 35 ++++ .../NOTICE | 14 ++ .../itest.bndrun | 70 +++++++ .../pom.xml | 16 ++ .../SuggestedAddonFinderServiceOSGiTest.java | 184 ++++++++++++++++++ itests/pom.xml | 1 + 10 files changed, 453 insertions(+), 61 deletions(-) create mode 100644 bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/NOTICE create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/pom.xml create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java diff --git a/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd new file mode 100644 index 00000000000..a719101b8fb --- /dev/null +++ b/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml index fefb7005ada..185032c253d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/pom.xml +++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml @@ -12,7 +12,7 @@ org.openhab.core.config.discovery.addon - openHAB Core :: Bundles :: Addon Suggestion Finder + openHAB Core :: Bundles :: Suggested Addon Finder @@ -32,4 +32,12 @@ + + + + src/main/resources + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java index 9109718a427..7e40eb8bee3 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java @@ -20,7 +20,8 @@ import org.eclipse.jdt.annotation.Nullable; /** - * DTO for serialization of a single addon suggestion candidate discovery method. + * DTO for serialization of a single addon suggestion candidate discovery + * method. * * @author Andrew Fiddian-Green - Initial contribution */ @@ -41,8 +42,9 @@ public String getMdnsServiceType() { } public Map getMatchProperties() { + List matchProperties = this.matchProperties; return matchProperties != null - ? matchProperties.stream().collect(Collectors.toMap(x -> x.getName(), x -> x.getRegex())) + ? matchProperties.stream().collect(Collectors.toMap(m -> m.getName(), m -> m.getRegex())) : Map.of(); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java index c190e9f340a..a435c5739d6 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java @@ -12,7 +12,10 @@ */ package org.openhab.core.config.discovery.addon.finder; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -79,40 +82,43 @@ public void serviceResolved(@Nullable ServiceEvent event) { } public static final String SERVICE_ID = "suggestions"; - public static final String SERVICE_NAME = "Suggested addon finder service"; + public static final String SERVICE_NAME = "suggested-addon-finder"; - private static final String FINDER_THREADPOOL_NAME = "addon-suggestion-finder"; + private static final String XML_RESOURCE_NAME = "suggested-addon-candidates.xml"; + private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); + private final Set addonServices = new CopyOnWriteArraySet<>(); private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinderService.class); - private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(FINDER_THREADPOOL_NAME); + private final List mdnsCandidates = new ArrayList<>(); private final NoOp noop = new NoOp(); + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Set suggestedAddonUids = ConcurrentHashMap.newKeySet(); - private final List mdnsCandidates = new ArrayList<>(); private final List upnpCandidates = new ArrayList<>(); - private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); - private final Set addonServices = new CopyOnWriteArraySet<>(); private final MDNSClient mdnsClient; private final UpnpService upnpService; - private @Nullable Future mdnsScanTask; private @Nullable Future upnpScanTask; + private @Nullable Future mdnsScanTask; @Activate - public AddonSuggestionFinderService(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) { + public AddonSuggestionFinderService(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) + throws XStreamException, IOException, IllegalStateException { this.mdnsClient = mdnsClient; this.upnpService = upnpService; + initialize(); + startScan(); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addAddonService(AddonService addonService) { - // exclude ourself from addonServices set in order to prevent recursion + public void addAddonService(AddonService addonService) { + // exclude ourself from addonServices set in order to prevent infinite recursion if (!SERVICE_ID.equals(addonService.getId())) { addonServices.add(addonService); } } - protected void removeAddonService(AddonService addonService) { + public void removeAddonService(AddonService addonService) { addonServices.remove(addonService); } @@ -127,9 +133,7 @@ public void close() throws Exception { @Override public @Nullable Addon getAddon(String id, @Nullable Locale locale) { - return addonServices.stream().filter(s -> !SERVICE_ID.equals(s.getId())).map(s -> s.getAddons(locale)) - .flatMap(Collection::stream).filter(a -> suggestedAddonUids.contains(a.getUid())) - .filter(a -> id.equals(a.getId())).findAny().orElse(null); + return getAddons(locale).stream().filter(a -> id.equals(a.getId())).findAny().orElse(null); } @Override @@ -158,36 +162,23 @@ public List getTypes(@Nullable Locale locale) { return List.of(AddonType.BINDING); } - @Override - public void install(String id) { - startScan(); - } - - @Override - public void uninstall(String id) { - Future task = upnpScanTask; - if (task != null) { - task.cancel(true); - } - task = mdnsScanTask; - if (task != null) { - task.cancel(true); - } - } - /** - * Initialize the AddonSuggestionFinder with XML data containing the list of + * Initialize the class by loading an XML file that contains the list of * potential addon candidates to be suggested. * - * @param xml an XML serial image. - * @throws XStreamException if the object cannot be deserialized. + * @throws IOException if there was an error reading the xml file. + * @throws XStreamException if the xml cannot be deserialized. */ - public void loadXML(String xml) throws XStreamException { - try { - close(); - } catch (Exception e) { - // exception should not occur + private void initialize() throws XStreamException, IOException, IllegalStateException { + ClassLoader loader = getClass().getClassLoader(); + if (loader == null) { + throw new IllegalStateException("Class loader is null"); + } + InputStream stream = loader.getResourceAsStream(XML_RESOURCE_NAME); + if (stream == null) { + throw new IllegalStateException("Resource stream is null"); } + String xml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); SuggestedAddonCandidates candidates = addonCandidatesSerializer.fromXML(xml); candidates.getCandidates().stream().forEach(c -> c.getDiscoveryMethods().stream().forEach(m -> { switch (m.getServiceType()) { @@ -205,36 +196,51 @@ public void loadXML(String xml) throws XStreamException { mdnsCandidates.forEach(c -> mdnsClient.addServiceListener(c.getMdnsServiceType(), noop)); } + @Override + public void install(String id) { + // note: startScan() is called from the constructor + } + @Override public void refreshSource() { startScan(); } - /** - * Run a scheduled task to search for matching addon suggestion candidates using - * the MDNS service. - */ - @SuppressWarnings("unlikely-arg-type") - private void startMdnsScan() { - Future task = mdnsScanTask; - if (task != null) { - task.cancel(false); - } - mdnsScanTask = scheduler - .submit(() -> mdnsCandidates.forEach(c -> Arrays.stream(mdnsClient.list(c.getMdnsServiceType())) - .filter(s -> c.equals(s)).forEach(s -> suggestionFound(c.getAddonId())))); + public boolean scanDone() { + Future mdnsScanTask = this.mdnsScanTask; + Future upnpScanTask = this.upnpScanTask; + return mdnsScanTask != null && mdnsScanTask.isDone() && upnpScanTask != null && upnpScanTask.isDone(); } /** * Start the search process to find addons to suggest to be installed. */ - public void startScan() { + private void startScan() { if (!mdnsCandidates.isEmpty()) { - startMdnsScan(); + startScanMdns(); } if (!upnpCandidates.isEmpty()) { - startUpnpScan(); + startScanUpnp(); + } + } + + /** + * Run a scheduled task to search for matching addon suggestion candidates using + * the MDNS service. + */ + @SuppressWarnings("unlikely-arg-type") + private void startScanMdns() { + Future task = mdnsScanTask; + if (task != null) { + task.cancel(false); } + mdnsScanTask = scheduler.submit(() -> { + mdnsCandidates.forEach(c -> { + Arrays.stream(mdnsClient.list(c.getMdnsServiceType())).filter(s -> c.equals(s)).forEach(s -> { + suggestionFound(c.getAddonId()); + }); + }); + }); } /** @@ -242,13 +248,18 @@ public void startScan() { * the UPnP service. */ @SuppressWarnings("unlikely-arg-type") - private void startUpnpScan() { + private void startScanUpnp() { Future task = upnpScanTask; if (task != null) { task.cancel(false); } - upnpScanTask = scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices().forEach( - d -> upnpCandidates.stream().filter(c -> c.equals(d)).forEach(c -> suggestionFound(c.getAddonId())))); + upnpScanTask = scheduler.submit(() -> { + upnpService.getRegistry().getRemoteDevices().forEach(d -> { + upnpCandidates.stream().filter(c -> c.equals(d)).forEach(c -> { + suggestionFound(c.getAddonId()); + }); + }); + }); } /** @@ -262,4 +273,16 @@ private synchronized void suggestionFound(String addonUid) { suggestedAddonUids.add(addonUid); } } + + @Override + public void uninstall(String id) { + Future task = upnpScanTask; + if (task != null) { + task.cancel(true); + } + task = mdnsScanTask; + if (task != null) { + task.cancel(true); + } + } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml b/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml new file mode 100644 index 00000000000..1e5bca0d959 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml @@ -0,0 +1,35 @@ + + + + + binding.hpprinter + + mdns + + rp + /.*\S.*/ + + + ty + ^hp + + _printer._tcp.local. + + + + binding.hue + + upnp + + modelName + Philips hue bridge + + + + mdns + _hue._tcp.local. + + + diff --git a/itests/org.openhab.core.config.discovery.addon.tests/NOTICE b/itests/org.openhab.core.config.discovery.addon.tests/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun new file mode 100644 index 00000000000..ba9440afbbd --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun @@ -0,0 +1,70 @@ +-include: ../itest-common.bndrun + +Bundle-SymbolicName: ${project.artifactId} +Fragment-Host: org.openhab.core.config.discovery.mdns + +-runrequires: bnd.identity;id='org.openhab.core.config.discovery.mdns.tests' + +# +# done +# +-runbundles: \ + org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\ + org.osgi.service.event;version='[1.4.0,1.4.1)',\ + org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\ + org.hamcrest;version='[2.2.0,2.2.1)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\ + jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ + org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\ + org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ + jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\ + org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\ + si-units;version='[2.1.0,2.1.1)',\ + si.uom.si-quantity;version='[2.1.0,2.1.1)',\ + org.osgi.util.function;version='[1.2.0,1.2.1)',\ + org.osgi.util.promise;version='[1.2.0,1.2.1)',\ + ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ + ch.qos.logback.core;version='[1.2.11,1.2.12)',\ + javax.jmdns;version='[3.5.8,3.5.9)',\ + biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\ + com.google.gson;version='[2.9.1,2.9.2)',\ + com.sun.jna;version='[5.12.1,5.12.2)',\ + org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ + org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\ + org.apache.felix.scr;version='[2.2.4,2.2.5)',\ + org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\ + org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\ + org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\ + org.osgi.service.component;version='[1.5.0,1.5.1)',\ + xstream;version='[1.4.20,1.4.21)',\ + junit-jupiter-api;version='[5.9.2,5.9.3)',\ + junit-jupiter-engine;version='[5.9.2,5.9.3)',\ + junit-platform-commons;version='[1.9.2,1.9.3)',\ + junit-platform-engine;version='[1.9.2,1.9.3)',\ + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ + org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.openhab.core;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.core;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.discovery.mdns;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.discovery.mdns.tests;version='[4.1.0,4.1.1)',\ + org.openhab.core.io.console;version='[4.1.0,4.1.1)',\ + org.openhab.core.io.transport.mdns;version='[4.1.0,4.1.1)',\ + org.openhab.core.test;version='[4.1.0,4.1.1)',\ + org.openhab.core.thing;version='[4.1.0,4.1.1)',\ + org.openhab.core.transform;version='[4.1.0,4.1.1)',\ + org.openhab.base-fixes;version='[1.0.0,1.0.1)',\ + javax.measure.unit-api;version='[2.2.0,2.2.1)',\ + org.apiguardian.api;version='[1.1.2,1.1.3)',\ + tech.units.indriya;version='[2.2.0,2.2.1)',\ + uom-lib-common;version='[2.2.0,2.2.1)',\ + io.methvin.directory-watcher;version='[0.18.0,0.18.1)' diff --git a/itests/org.openhab.core.config.discovery.addon.tests/pom.xml b/itests/org.openhab.core.config.discovery.addon.tests/pom.xml new file mode 100644 index 00000000000..7097d4cf96d --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + + org.openhab.core.itests + org.openhab.core.reactor.itests + 4.1.0-SNAPSHOT + + + org.openhab.core.config.discovery.addons.tests + + openHAB Core :: Integration Tests :: Suggested Addon Finder Tests + + diff --git a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java new file mode 100644 index 00000000000..fec9a951ce0 --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java @@ -0,0 +1,184 @@ +/** + * 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.config.discovery.addon.tests.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.jupnp.UpnpService; +import org.jupnp.model.ValidationException; +import org.jupnp.model.meta.DeviceDetails; +import org.jupnp.model.meta.ManufacturerDetails; +import org.jupnp.model.meta.ModelDetails; +import org.jupnp.model.meta.RemoteDevice; +import org.jupnp.model.meta.RemoteDeviceIdentity; +import org.jupnp.model.meta.RemoteService; +import org.jupnp.model.types.DeviceType; +import org.jupnp.model.types.UDN; +import org.mockito.Mockito; +import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonService; +import org.openhab.core.config.discovery.addon.finder.AddonSuggestionFinderService; +import org.openhab.core.io.transport.mdns.MDNSClient; +import org.openhab.core.test.java.JavaOSGiTest; + +import com.thoughtworks.xstream.XStreamException; + +/** + * Integration tests for the {@link SuggestedAddonFinderService}. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +public class SuggestedAddonFinderServiceOSGiTest extends JavaOSGiTest { + + private @NonNullByDefault({}) MDNSClient mdnsClient; + private @NonNullByDefault({}) UpnpService upnpService; + private @NonNullByDefault({}) AddonService addonService; + private @NonNullByDefault({}) AddonSuggestionFinderService addonSuggestionFinderService; + + @BeforeAll + public void setup() { + setupAddonServiceMock(); + setupMdnsClientMock(); + setupUpnpServiceMock(); + setupService(); + } + + private void setupAddonServiceMock() { + // create the mock + addonService = mock(AddonService.class); + List addons = new ArrayList<>(); + addons.add(Addon.create("binding.hue").withType("binding").withId("hue").build()); + addons.add(Addon.create("binding.hpprinter").withType("binding").withId("hpprinter").build()); + when(addonService.getAddons(Locale.US)).thenReturn(addons); + + // check that it works + assertNotNull(addonService); + assertEquals(2, addonService.getAddons(Locale.US).size()); + assertEquals("binding.hue", addonService.getAddons(Locale.US).get(0).getUid()); + assertEquals("binding.hpprinter", addonService.getAddons(Locale.US).get(1).getUid()); + } + + private void setupMdnsClientMock() { + // create the mock + mdnsClient = mock(MDNSClient.class); + ServiceInfo hueService = ServiceInfo.create("test", "hue", 0, 0, 0, false, "hue service"); + when(mdnsClient.list("_hue._tcp.local.")).thenReturn(new ServiceInfo[] { hueService }); + ServiceInfo hpService = ServiceInfo.create("test", "hpprinter", 0, 0, 0, false, "hp printer service"); + hpService.setText(Map.of("ty", "hp printer", "rp", "anything")); + when(mdnsClient.list("_printer._tcp.local.")).thenReturn(new ServiceInfo[] { hpService }); + + // check that it works + assertNotNull(mdnsClient); + ServiceInfo[] result; + result = mdnsClient.list("_printer._tcp.local."); + assertEquals(1, result.length); + assertEquals("hpprinter", result[0].getName()); + assertEquals(2, Collections.list(result[0].getPropertyNames()).size()); + assertEquals("hp printer", result[0].getPropertyString("ty")); + result = mdnsClient.list("_hue._tcp.local."); + assertEquals(1, result.length); + assertEquals("hue", result[0].getName()); + } + + private void setupService() { + // create the service + try { + addonSuggestionFinderService = new AddonSuggestionFinderService(mdnsClient, upnpService); + } catch (IOException e) { + fail("Error loading XML"); + } catch (XStreamException e) { + fail("Error parsing XML"); + } + addonSuggestionFinderService.addAddonService(addonService); + + // check that it exists + assertNotNull(addonSuggestionFinderService); + } + + private void setupUpnpServiceMock() { + // create the mock + upnpService = mock(UpnpService.class, Mockito.RETURNS_DEEP_STUBS); + URL url = null; + try { + url = new URL("http://www.openhab.org/"); + } catch (MalformedURLException e) { + fail("MalformedURLException"); + } + UDN udn = new UDN("udn"); + InetAddress address = null; + try { + address = InetAddress.getByName("127.0.0.1"); + } catch (UnknownHostException e) { + fail("UnknownHostException"); + } + RemoteDeviceIdentity identity = new RemoteDeviceIdentity(udn, 0, url, new byte[] {}, address); + DeviceType type = new DeviceType("nameSpace", "type"); + ManufacturerDetails manDetails = new ManufacturerDetails("manufacturer", "manufacturerURI"); + ModelDetails modDetails = new ModelDetails("modelName", "modelDescription", "modelNumber", "modelURI"); + DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber", + "000123456789"); + List<@Nullable RemoteDevice> remoteDevice = new ArrayList<>(); + try { + remoteDevice.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null)); + } catch (ValidationException e1) { + fail("ValidationException"); + } + when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevice); + + // check that it works + assertNotNull(upnpService); + List result = new ArrayList<>(upnpService.getRegistry().getRemoteDevices()); + assertEquals(1, result.size()); + RemoteDevice device = result.get(0); + assertEquals("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()); + assertEquals("serialNumber", device.getDetails().getSerialNumber()); + } + + @Test + public void testGetAddons() { + // give the scan tasks some time to complete + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + assertTrue(addonSuggestionFinderService.scanDone()); + List addons = addonSuggestionFinderService.getAddons(Locale.US); + assertEquals(2, addons.size()); + } +} diff --git a/itests/pom.xml b/itests/pom.xml index f6884de2a79..367ba10384d 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -26,6 +26,7 @@ org.openhab.core.automation.integration.tests org.openhab.core.addon.tests org.openhab.core.config.core.tests + org.openhab.core.config.discovery.addon.tests org.openhab.core.config.discovery.mdns.tests org.openhab.core.config.discovery.tests org.openhab.core.config.discovery.usbserial.tests From e90266ff70b7ee4b94447bc579c22751728724d7 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 20 Sep 2023 23:54:09 +0100 Subject: [PATCH 16/98] [suggestion-finder] better logging Signed-off-by: Andrew Fiddian-Green --- .../addon/finder/AddonSuggestionFinderService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java index a435c5739d6..3f99a6b063a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java @@ -39,6 +39,7 @@ import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.discovery.addon.candidate.MdnsCandidate; import org.openhab.core.config.discovery.addon.candidate.UpnpCandidate; +import org.openhab.core.config.discovery.addon.dto.ServiceType; import org.openhab.core.config.discovery.addon.dto.SuggestedAddonCandidates; import org.openhab.core.config.discovery.addon.xml.AddonCandidatesSerializer; import org.openhab.core.io.transport.mdns.MDNSClient; @@ -237,7 +238,7 @@ private void startScanMdns() { mdnsScanTask = scheduler.submit(() -> { mdnsCandidates.forEach(c -> { Arrays.stream(mdnsClient.list(c.getMdnsServiceType())).filter(s -> c.equals(s)).forEach(s -> { - suggestionFound(c.getAddonId()); + suggestionFound(ServiceType.MDNS, c.getAddonId()); }); }); }); @@ -256,7 +257,7 @@ private void startScanUpnp() { upnpScanTask = scheduler.submit(() -> { upnpService.getRegistry().getRemoteDevices().forEach(d -> { upnpCandidates.stream().filter(c -> c.equals(d)).forEach(c -> { - suggestionFound(c.getAddonId()); + suggestionFound(ServiceType.UPNP, c.getAddonId()); }); }); }); @@ -265,11 +266,12 @@ private void startScanUpnp() { /** * Called back when a new addon suggestion is found. * + * @param origin the service type that found the addon. * @param addonUid the Uid of the found addon. */ - private synchronized void suggestionFound(String addonUid) { + private synchronized void suggestionFound(ServiceType origin, String addonUid) { + logger.debug("Service {} found suggested addon id:{}", origin, addonUid); if (!suggestedAddonUids.contains(addonUid)) { - logger.debug("found suggested addon id:{}", addonUid); suggestedAddonUids.add(addonUid); } } From 24fbaa3b25f5604ebae96423e2b4621bd49546e8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 21 Sep 2023 13:29:49 +0100 Subject: [PATCH 17/98] [suggestion-finder] various tweaks Signed-off-by: Andrew Fiddian-Green --- .../AddonSuggestionFinderService.java | 25 ++----- .../resources/suggested-addon-candidates.xml | 4 +- .../core/internal/addons/AddonResource.java | 2 +- .../.classpath | 29 ++++++++ .../.project | 23 ++++++ .../itest.bndrun | 8 +- .../pom.xml | 2 +- .../SuggestedAddonFinderServiceOSGiTest.java | 74 +++++++++++-------- 8 files changed, 108 insertions(+), 59 deletions(-) rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/{finder => }/AddonSuggestionFinderService.java (91%) create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/.classpath create mode 100644 itests/org.openhab.core.config.discovery.addon.tests/.project rename itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/{internal => }/SuggestedAddonFinderServiceOSGiTest.java (76%) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java similarity index 91% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 3f99a6b063a..fad90dfcbbe 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finder/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.finder; +package org.openhab.core.config.discovery.addon; import java.io.IOException; import java.io.InputStream; @@ -49,8 +49,6 @@ import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.XStreamException; @@ -89,7 +87,6 @@ public void serviceResolved(@Nullable ServiceEvent event) { private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); private final Set addonServices = new CopyOnWriteArraySet<>(); - private final Logger logger = LoggerFactory.getLogger(AddonSuggestionFinderService.class); private final List mdnsCandidates = new ArrayList<>(); private final NoOp noop = new NoOp(); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); @@ -235,13 +232,9 @@ private void startScanMdns() { if (task != null) { task.cancel(false); } - mdnsScanTask = scheduler.submit(() -> { - mdnsCandidates.forEach(c -> { - Arrays.stream(mdnsClient.list(c.getMdnsServiceType())).filter(s -> c.equals(s)).forEach(s -> { - suggestionFound(ServiceType.MDNS, c.getAddonId()); - }); - }); - }); + mdnsScanTask = scheduler + .submit(() -> mdnsCandidates.forEach(c -> Arrays.stream(mdnsClient.list(c.getMdnsServiceType())) + .filter(s -> c.equals(s)).forEach(s -> suggestionFound(ServiceType.MDNS, c.getAddonId())))); } /** @@ -254,13 +247,8 @@ private void startScanUpnp() { if (task != null) { task.cancel(false); } - upnpScanTask = scheduler.submit(() -> { - upnpService.getRegistry().getRemoteDevices().forEach(d -> { - upnpCandidates.stream().filter(c -> c.equals(d)).forEach(c -> { - suggestionFound(ServiceType.UPNP, c.getAddonId()); - }); - }); - }); + upnpScanTask = scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices().forEach(d -> upnpCandidates + .stream().filter(c -> c.equals(d)).forEach(c -> suggestionFound(ServiceType.UPNP, c.getAddonId())))); } /** @@ -270,7 +258,6 @@ private void startScanUpnp() { * @param addonUid the Uid of the found addon. */ private synchronized void suggestionFound(ServiceType origin, String addonUid) { - logger.debug("Service {} found suggested addon id:{}", origin, addonUid); if (!suggestedAddonUids.contains(addonUid)) { suggestedAddonUids.add(addonUid); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml b/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml index 1e5bca0d959..e41f2c4de8c 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml @@ -9,11 +9,11 @@ mdns rp - /.*\S.*/ + .* ty - ^hp + hp (.*) _printer._tcp.local. diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 237465165d2..6783c2dcabc 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -56,7 +56,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.config.discovery.addon.finder.AddonSuggestionFinderService; +import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.JSONResponse; diff --git a/itests/org.openhab.core.config.discovery.addon.tests/.classpath b/itests/org.openhab.core.config.discovery.addon.tests/.classpath new file mode 100644 index 00000000000..9e55698cddc --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/.classpath @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/itests/org.openhab.core.config.discovery.addon.tests/.project b/itests/org.openhab.core.config.discovery.addon.tests/.project new file mode 100644 index 00000000000..d8eff8b2d91 --- /dev/null +++ b/itests/org.openhab.core.config.discovery.addon.tests/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.config.discovery.addon.tests + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun index ba9440afbbd..6a05c8a3cfb 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun @@ -1,9 +1,9 @@ -include: ../itest-common.bndrun Bundle-SymbolicName: ${project.artifactId} -Fragment-Host: org.openhab.core.config.discovery.mdns +Fragment-Host: org.openhab.core.config.discovery.addon --runrequires: bnd.identity;id='org.openhab.core.config.discovery.mdns.tests' +-runrequires: bnd.identity;id='org.openhab.core.config.discovery.addon.tests' # # done @@ -55,8 +55,8 @@ Fragment-Host: org.openhab.core.config.discovery.mdns org.openhab.core;version='[4.1.0,4.1.1)',\ org.openhab.core.config.core;version='[4.1.0,4.1.1)',\ org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.discovery.mdns;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.discovery.mdns.tests;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.discovery.addon;version='[4.1.0,4.1.1)',\ + org.openhab.core.config.discovery.addon.tests;version='[4.1.0,4.1.1)',\ org.openhab.core.io.console;version='[4.1.0,4.1.1)',\ org.openhab.core.io.transport.mdns;version='[4.1.0,4.1.1)',\ org.openhab.core.test;version='[4.1.0,4.1.1)',\ diff --git a/itests/org.openhab.core.config.discovery.addon.tests/pom.xml b/itests/org.openhab.core.config.discovery.addon.tests/pom.xml index 7097d4cf96d..2522a4a55fb 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/pom.xml +++ b/itests/org.openhab.core.config.discovery.addon.tests/pom.xml @@ -9,7 +9,7 @@ 4.1.0-SNAPSHOT - org.openhab.core.config.discovery.addons.tests + org.openhab.core.config.discovery.addon.tests openHAB Core :: Integration Tests :: Suggested Addon Finder Tests diff --git a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java similarity index 76% rename from itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java rename to itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java index fec9a951ce0..02c75ab4b61 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/internal/SuggestedAddonFinderServiceOSGiTest.java +++ b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java @@ -10,12 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.tests.internal; +package org.openhab.core.config.discovery.addon.tests; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -51,7 +55,7 @@ import org.mockito.Mockito; import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonService; -import org.openhab.core.config.discovery.addon.finder.AddonSuggestionFinderService; +import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.core.test.java.JavaOSGiTest; @@ -73,35 +77,52 @@ public class SuggestedAddonFinderServiceOSGiTest extends JavaOSGiTest { @BeforeAll public void setup() { - setupAddonServiceMock(); - setupMdnsClientMock(); - setupUpnpServiceMock(); - setupService(); + setupMockAddonService(); + setupMockMdnsClient(); + setupMockUpnpService(); + createAddonSuggestionFinderService(); } - private void setupAddonServiceMock() { + private void createAddonSuggestionFinderService() { + // create the service + try { + addonSuggestionFinderService = new AddonSuggestionFinderService(mdnsClient, upnpService); + } catch (IOException e) { + fail("Error loading XML"); + } catch (XStreamException e) { + fail("Error parsing XML"); + } + addonSuggestionFinderService.addAddonService(addonService); + + // check that it exists + assertNotNull(addonSuggestionFinderService); + } + + private void setupMockAddonService() { // create the mock addonService = mock(AddonService.class); List addons = new ArrayList<>(); addons.add(Addon.create("binding.hue").withType("binding").withId("hue").build()); addons.add(Addon.create("binding.hpprinter").withType("binding").withId("hpprinter").build()); - when(addonService.getAddons(Locale.US)).thenReturn(addons); + when(addonService.getAddons(any(Locale.class))).thenReturn(addons); // check that it works assertNotNull(addonService); assertEquals(2, addonService.getAddons(Locale.US).size()); - assertEquals("binding.hue", addonService.getAddons(Locale.US).get(0).getUid()); - assertEquals("binding.hpprinter", addonService.getAddons(Locale.US).get(1).getUid()); + assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding.hue".equals(a.getUid()))); + assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding.hpprinter".equals(a.getUid()))); + assertFalse(addonService.getAddons(Locale.US).stream().anyMatch(a -> "aardvark".equals(a.getUid()))); } - private void setupMdnsClientMock() { + private void setupMockMdnsClient() { // create the mock mdnsClient = mock(MDNSClient.class); - ServiceInfo hueService = ServiceInfo.create("test", "hue", 0, 0, 0, false, "hue service"); - when(mdnsClient.list("_hue._tcp.local.")).thenReturn(new ServiceInfo[] { hueService }); - ServiceInfo hpService = ServiceInfo.create("test", "hpprinter", 0, 0, 0, false, "hp printer service"); + when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {}); + ServiceInfo hueService = ServiceInfo.create("mdnsTest", "hue", 0, 0, 0, false, "hue service"); + when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService }); + ServiceInfo hpService = ServiceInfo.create("mdnsTest", "hpprinter", 0, 0, 0, false, "hp printer service"); hpService.setText(Map.of("ty", "hp printer", "rp", "anything")); - when(mdnsClient.list("_printer._tcp.local.")).thenReturn(new ServiceInfo[] { hpService }); + when(mdnsClient.list(eq("_printer._tcp.local."))).thenReturn(new ServiceInfo[] { hpService }); // check that it works assertNotNull(mdnsClient); @@ -114,24 +135,11 @@ private void setupMdnsClientMock() { result = mdnsClient.list("_hue._tcp.local."); assertEquals(1, result.length); assertEquals("hue", result[0].getName()); + result = mdnsClient.list("aardvark"); + assertEquals(0, result.length); } - private void setupService() { - // create the service - try { - addonSuggestionFinderService = new AddonSuggestionFinderService(mdnsClient, upnpService); - } catch (IOException e) { - fail("Error loading XML"); - } catch (XStreamException e) { - fail("Error parsing XML"); - } - addonSuggestionFinderService.addAddonService(addonService); - - // check that it exists - assertNotNull(addonSuggestionFinderService); - } - - private void setupUpnpServiceMock() { + private void setupMockUpnpService() { // create the mock upnpService = mock(UpnpService.class, Mockito.RETURNS_DEEP_STUBS); URL url = null; @@ -150,7 +158,7 @@ private void setupUpnpServiceMock() { RemoteDeviceIdentity identity = new RemoteDeviceIdentity(udn, 0, url, new byte[] {}, address); DeviceType type = new DeviceType("nameSpace", "type"); ManufacturerDetails manDetails = new ManufacturerDetails("manufacturer", "manufacturerURI"); - ModelDetails modDetails = new ModelDetails("modelName", "modelDescription", "modelNumber", "modelURI"); + ModelDetails modDetails = new ModelDetails("Philips hue bridge", "modelDescription", "modelNumber", "modelURI"); DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber", "000123456789"); List<@Nullable RemoteDevice> remoteDevice = new ArrayList<>(); @@ -180,5 +188,7 @@ public void testGetAddons() { assertTrue(addonSuggestionFinderService.scanDone()); List addons = addonSuggestionFinderService.getAddons(Locale.US); assertEquals(2, addons.size()); + assertTrue(addons.stream().anyMatch(a -> "binding.hue".equals(a.getUid()))); + assertTrue(addons.stream().anyMatch(a -> "binding.hpprinter".equals(a.getUid()))); } } From 5c5b9a9eeb2537c9c679445bffa6fe2d7204952a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 22 Sep 2023 09:51:19 +0100 Subject: [PATCH 18/98] [suggestion-finder] fix some dependencies Signed-off-by: Andrew Fiddian-Green --- .../.classpath | 5 +++++ .../org.openhab.core.config.discovery.addon/pom.xml | 5 ----- .../itest.bndrun | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/.classpath b/bundles/org.openhab.core.config.discovery.addon/.classpath index e9d63b05acb..8f89649a811 100644 --- a/bundles/org.openhab.core.config.discovery.addon/.classpath +++ b/bundles/org.openhab.core.config.discovery.addon/.classpath @@ -25,5 +25,10 @@ + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml index 185032c253d..3af4946dfe3 100644 --- a/bundles/org.openhab.core.config.discovery.addon/pom.xml +++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml @@ -20,11 +20,6 @@ org.openhab.core.config.discovery.mdns ${project.version} - - org.openhab.core.bundles - org.openhab.core.config.discovery.upnp - ${project.version} - org.openhab.core.bundles org.openhab.core.addon diff --git a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun index 6a05c8a3cfb..cd67d023171 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun +++ b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun @@ -54,17 +54,18 @@ Fragment-Host: org.openhab.core.config.discovery.addon org.objenesis;version='[3.3.0,3.3.1)',\ org.openhab.core;version='[4.1.0,4.1.1)',\ org.openhab.core.config.core;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.discovery;version='[4.1.0,4.1.1)',\ org.openhab.core.config.discovery.addon;version='[4.1.0,4.1.1)',\ org.openhab.core.config.discovery.addon.tests;version='[4.1.0,4.1.1)',\ - org.openhab.core.io.console;version='[4.1.0,4.1.1)',\ org.openhab.core.io.transport.mdns;version='[4.1.0,4.1.1)',\ org.openhab.core.test;version='[4.1.0,4.1.1)',\ - org.openhab.core.thing;version='[4.1.0,4.1.1)',\ - org.openhab.core.transform;version='[4.1.0,4.1.1)',\ org.openhab.base-fixes;version='[1.0.0,1.0.1)',\ javax.measure.unit-api;version='[2.2.0,2.2.1)',\ org.apiguardian.api;version='[1.1.2,1.1.3)',\ tech.units.indriya;version='[2.2.0,2.2.1)',\ uom-lib-common;version='[2.2.0,2.2.1)',\ - io.methvin.directory-watcher;version='[0.18.0,0.18.1)' + io.methvin.directory-watcher;version='[0.18.0,0.18.1)',\ + org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\ + org.jupnp;version='[2.7.1,2.7.2)',\ + org.openhab.core.addon;version='[4.1.0,4.1.1)',\ + org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\ + org.osgi.service.cm;version='[1.6.0,1.6.1)' From 3cd54a7701e10aa88265b179bd0d08cdacc3367a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 11 Oct 2023 22:12:33 +0100 Subject: [PATCH 19/98] [suggestion-finder] fix some dependencies Signed-off-by: Andrew Fiddian-Green --- .../core/addon/AddonDiscoveryMethod.java | 50 ++-- .../addon/AddonDiscoveryServiceType.java} | 4 +- .../org/openhab/core/addon/AddonInfo.java | 17 +- .../core/addon/AddonMatchProperty.java | 43 +-- .../addon/AddonSuggestionFinderService.java | 252 ++++-------------- .../addon/candidate/BaseCandidate.java | 54 ---- .../addon/candidate/MdnsCandidate.java | 55 ---- .../addon/candidate/UpnpCandidate.java | 49 ---- .../config/discovery/addon/dto/Candidate.java | 49 ---- .../discovery/addon/dto/DiscoveryMethod.java | 66 ----- .../discovery/addon/dto/MatchProperty.java | 42 --- .../addon/dto/SuggestedAddonCandidates.java | 38 --- .../addon/finders/AddonSuggestionFinder.java | 73 +++++ .../finders/MDNSAddonSuggestionFinder.java | 87 ++++++ .../finders/UpnpAddonSuggestionFinder.java | 56 ++++ .../addon/xml/AddonCandidatesSerializer.java | 75 ------ .../resources/suggested-addon-candidates.xml | 35 --- .../core/internal/addons/AddonResource.java | 3 +- .../openhab-core/src/main/feature/feature.xml | 16 ++ ...AddonSuggestionFinderServiceOSGiTest.java} | 87 ++++-- 20 files changed, 379 insertions(+), 772 deletions(-) rename bundles/{org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java => org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java} (88%) delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml rename itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/{SuggestedAddonFinderServiceOSGiTest.java => AddonSuggestionFinderServiceOSGiTest.java} (68%) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java index 88eb8669819..264954f9c73 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -13,7 +13,8 @@ package org.openhab.core.addon; import java.util.List; -import java.util.Objects; +import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -25,12 +26,13 @@ */ @NonNullByDefault public class AddonDiscoveryMethod { - private @NonNullByDefault({}) String serviceType; - private @Nullable String mdnsServiceType; + private @Nullable String serviceType; private @Nullable List matchProperties; + private @Nullable String mdnsServiceType; - public String getServiceType() { - return serviceType.toLowerCase(); + public AddonDiscoveryServiceType getServiceType() { + String serviceType = this.serviceType; + return AddonDiscoveryServiceType.valueOf(serviceType != null ? serviceType.toUpperCase() : ""); } public String getMdnsServiceType() { @@ -38,44 +40,26 @@ public String getMdnsServiceType() { return mdnsServiceType != null ? mdnsServiceType : ""; } - public List getMatchProperties() { + public Map getPropertyRegexMap() { List matchProperties = this.matchProperties; - return matchProperties != null ? matchProperties : List.of(); + return matchProperties != null + ? matchProperties.stream().collect(Collectors.toMap(m -> m.getName(), m -> m.getRegex())) + : Map.of(); } - public AddonDiscoveryMethod setServiceType(String serviceType) { - this.serviceType = serviceType.toLowerCase(); + public AddonDiscoveryMethod setServiceType(AddonDiscoveryServiceType serviceType) { + this.serviceType = serviceType.name().toLowerCase(); return this; } - public AddonDiscoveryMethod setMdnsServiceType(@Nullable String mdnsServiceType) { + public AddonDiscoveryMethod setMdnsServiceType(String mdnsServiceType) { this.mdnsServiceType = mdnsServiceType; return this; } - public AddonDiscoveryMethod setMatchProperties(@Nullable List matchProperties) { - this.matchProperties = matchProperties; + public AddonDiscoveryMethod setMatchProperties(Map matchProperties) { + this.matchProperties = matchProperties.entrySet().stream() + .map(e -> new AddonMatchProperty(e.getKey(), e.getValue())).toList(); return this; } - - @Override - public int hashCode() { - return Objects.hash(serviceType, mdnsServiceType, matchProperties); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - AddonDiscoveryMethod other = (AddonDiscoveryMethod) obj; - return Objects.equals(serviceType, other.serviceType) && Objects.equals(mdnsServiceType, other.mdnsServiceType) - && Objects.equals(matchProperties, other.matchProperties); - } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java similarity index 88% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java rename to bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java index 23ad110de24..a9526b50246 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/ServiceType.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.dto; +package org.openhab.core.addon; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -20,7 +20,7 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public enum ServiceType { +public enum AddonDiscoveryServiceType { MDNS, UPNP } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index 8e45c47c918..760d6e996b5 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -47,10 +47,10 @@ public class AddonInfo implements Identifiable { private @Nullable String sourceBundle; private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, @Nullable String uid, String name, String description, - @Nullable String connection, List countries, @Nullable String configDescriptionURI, - @Nullable String serviceId, @Nullable String sourceBundle, - @Nullable List discoveryMethods) throws IllegalArgumentException { + private AddonInfo(String id, String type, String name, String description, @Nullable String connection, + List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, + @Nullable String sourceBundle, @Nullable List discoveryMethods) + throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -193,11 +193,6 @@ private Builder(AddonInfo addonInfo) { this.discoveryMethods = addonInfo.discoveryMethods; } - public Builder withUID(@Nullable String uid) { - this.uid = uid; - return this; - } - public Builder withName(String name) { this.name = name; return this; @@ -250,8 +245,8 @@ public Builder withDiscoveryMethods(@Nullable List discove * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, - serviceId, sourceBundle, discoveryMethods); + return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, + sourceBundle, discoveryMethods); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java index ac5ccebe9b3..06943b64fa5 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -12,9 +12,6 @@ */ package org.openhab.core.addon; -import java.util.Objects; -import java.util.regex.Pattern; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -25,49 +22,21 @@ */ @NonNullByDefault public class AddonMatchProperty { - private @NonNullByDefault({}) String name; - private @NonNullByDefault({}) String regex; - private transient @NonNullByDefault({}) Pattern pattern; + private @Nullable String name; + private @Nullable String regex; public AddonMatchProperty(String name, String regex) { this.name = name; this.regex = regex; - this.pattern = null; } public String getName() { - return name; - } - - public Pattern getPattern() { - Pattern pattern = this.pattern; - if (pattern == null) { - this.pattern = Pattern.compile(regex); - } - return this.pattern; + String name = this.name; + return name != null ? name : ""; } public String getRegex() { - return regex; - } - - @Override - public int hashCode() { - return Objects.hash(name, regex); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - AddonMatchProperty other = (AddonMatchProperty) obj; - return Objects.equals(name, other.name) && Objects.equals(regex, other.regex); + String regex = this.regex; + return regex != null ? regex : ""; } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index fad90dfcbbe..1ebc26d3b71 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -12,36 +12,29 @@ */ package org.openhab.core.config.discovery.addon; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; - -import javax.jmdns.ServiceEvent; -import javax.jmdns.ServiceListener; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.jupnp.UpnpService; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonService; -import org.openhab.core.addon.AddonType; import org.openhab.core.common.ThreadPoolManager; -import org.openhab.core.config.discovery.addon.candidate.MdnsCandidate; -import org.openhab.core.config.discovery.addon.candidate.UpnpCandidate; -import org.openhab.core.config.discovery.addon.dto.ServiceType; -import org.openhab.core.config.discovery.addon.dto.SuggestedAddonCandidates; -import org.openhab.core.config.discovery.addon.xml.AddonCandidatesSerializer; +import org.openhab.core.config.discovery.addon.finders.AddonSuggestionFinder; +import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; +import org.openhab.core.config.discovery.addon.finders.UpnpAddonSuggestionFinder; +import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -50,8 +43,6 @@ import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; -import com.thoughtworks.xstream.XStreamException; - /** * This is a {@link AddonSuggestionFinderService} which discovers suggested * addons for the user to install. @@ -59,61 +50,41 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(immediate = true, service = AddonService.class, name = AddonSuggestionFinderService.SERVICE_NAME) -public class AddonSuggestionFinderService implements AddonService, AutoCloseable { - - /** - * Inner ServiceListener implementation that ignores call-backs. - */ - private static class NoOp implements ServiceListener { - - @Override - public void serviceAdded(@Nullable ServiceEvent event) { - } - - @Override - public void serviceRemoved(@Nullable ServiceEvent event) { - } +@Component(immediate = true, service = AddonSuggestionFinderService.class, name = AddonSuggestionFinderService.SERVICE_NAME) +public class AddonSuggestionFinderService implements AutoCloseable { - @Override - public void serviceResolved(@Nullable ServiceEvent event) { - } - } - - public static final String SERVICE_ID = "suggestions"; public static final String SERVICE_NAME = "suggested-addon-finder"; - private static final String XML_RESOURCE_NAME = "suggested-addon-candidates.xml"; - - private final AddonCandidatesSerializer addonCandidatesSerializer = new AddonCandidatesSerializer(); - private final Set addonServices = new CopyOnWriteArraySet<>(); - private final List mdnsCandidates = new ArrayList<>(); - private final NoOp noop = new NoOp(); + private final Set addonServices = ConcurrentHashMap.newKeySet(); + private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); - private final Set suggestedAddonUids = ConcurrentHashMap.newKeySet(); - private final List upnpCandidates = new ArrayList<>(); + private final List finders = Collections.synchronizedList(new ArrayList<>()); + private final List> finderTasks = Collections.synchronizedList(new ArrayList<>()); + private final LocaleProvider localeProvider; - private final MDNSClient mdnsClient; - private final UpnpService upnpService; + @Activate + public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider, @Reference MDNSClient mdnsClient, + @Reference UpnpService upnpService) { + this.localeProvider = localeProvider; + finders.add(new MDNSAddonSuggestionFinder(mdnsClient)); + finders.add(new UpnpAddonSuggestionFinder(upnpService)); + } - private @Nullable Future upnpScanTask; - private @Nullable Future mdnsScanTask; + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + if (!addonInfoProviders.contains(addonInfoProvider)) { + addonInfoProviders.add(addonInfoProvider); + scanStart(); + } + } - @Activate - public AddonSuggestionFinderService(@Reference MDNSClient mdnsClient, @Reference UpnpService upnpService) - throws XStreamException, IOException, IllegalStateException { - this.mdnsClient = mdnsClient; - this.upnpService = upnpService; - initialize(); - startScan(); + public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + addonInfoProviders.remove(addonInfoProvider); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonService(AddonService addonService) { - // exclude ourself from addonServices set in order to prevent infinite recursion - if (!SERVICE_ID.equals(addonService.getId())) { - addonServices.add(addonService); - } + addonServices.add(addonService); } public void removeAddonService(AddonService addonService) { @@ -123,155 +94,34 @@ public void removeAddonService(AddonService addonService) { @Deactivate @Override public void close() throws Exception { - mdnsCandidates.forEach(c -> mdnsClient.removeServiceListener(c.getMdnsServiceType(), noop)); - mdnsCandidates.clear(); - upnpCandidates.clear(); - suggestedAddonUids.clear(); - } - - @Override - public @Nullable Addon getAddon(String id, @Nullable Locale locale) { - return getAddons(locale).stream().filter(a -> id.equals(a.getId())).findAny().orElse(null); - } - - @Override - public @Nullable String getAddonId(URI addonURI) { - return null; + finderTasks.forEach(t -> t.cancel(true)); + finderTasks.clear(); + finders.forEach(f -> f.reset()); + finders.clear(); } - @Override - public List getAddons(@Nullable Locale locale) { - return addonServices.stream().filter(s -> !SERVICE_ID.equals(s.getId())).map(s -> s.getAddons(locale)) - .flatMap(Collection::stream).filter(a -> suggestedAddonUids.contains(a.getUid())).toList(); - } + public List getSuggestedAddons(@Nullable Locale locale) { + Set uids = finders.stream().map(f -> f.getAddonSuggestionUIDs()).flatMap(Collection::stream) + .collect(Collectors.toSet()); - @Override - public String getId() { - return SERVICE_ID; - } - - @Override - public String getName() { - return SERVICE_NAME; - } - - @Override - public List getTypes(@Nullable Locale locale) { - return List.of(AddonType.BINDING); - } - - /** - * Initialize the class by loading an XML file that contains the list of - * potential addon candidates to be suggested. - * - * @throws IOException if there was an error reading the xml file. - * @throws XStreamException if the xml cannot be deserialized. - */ - private void initialize() throws XStreamException, IOException, IllegalStateException { - ClassLoader loader = getClass().getClassLoader(); - if (loader == null) { - throw new IllegalStateException("Class loader is null"); - } - InputStream stream = loader.getResourceAsStream(XML_RESOURCE_NAME); - if (stream == null) { - throw new IllegalStateException("Resource stream is null"); - } - String xml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - SuggestedAddonCandidates candidates = addonCandidatesSerializer.fromXML(xml); - candidates.getCandidates().stream().forEach(c -> c.getDiscoveryMethods().stream().forEach(m -> { - switch (m.getServiceType()) { - case MDNS: - mdnsCandidates - .add(new MdnsCandidate(c.getAddonUid(), m.getMatchProperties(), m.getMdnsServiceType())); - break; - case UPNP: - upnpCandidates.add(new UpnpCandidate(c.getAddonUid(), m.getMatchProperties())); - break; - default: - break; - } - })); - mdnsCandidates.forEach(c -> mdnsClient.addServiceListener(c.getMdnsServiceType(), noop)); - } - - @Override - public void install(String id) { - // note: startScan() is called from the constructor - } - - @Override - public void refreshSource() { - startScan(); + return addonServices.stream().map(s -> s.getAddons(locale)).flatMap(Collection::stream) + .filter(a -> uids.contains(a.getUid())).toList(); } public boolean scanDone() { - Future mdnsScanTask = this.mdnsScanTask; - Future upnpScanTask = this.upnpScanTask; - return mdnsScanTask != null && mdnsScanTask.isDone() && upnpScanTask != null && upnpScanTask.isDone(); + return finders.stream().allMatch(f -> f.scanDone()); } - /** - * Start the search process to find addons to suggest to be installed. - */ - private void startScan() { - if (!mdnsCandidates.isEmpty()) { - startScanMdns(); - } - if (!upnpCandidates.isEmpty()) { - startScanUpnp(); - } - } - - /** - * Run a scheduled task to search for matching addon suggestion candidates using - * the MDNS service. - */ - @SuppressWarnings("unlikely-arg-type") - private void startScanMdns() { - Future task = mdnsScanTask; - if (task != null) { - task.cancel(false); - } - mdnsScanTask = scheduler - .submit(() -> mdnsCandidates.forEach(c -> Arrays.stream(mdnsClient.list(c.getMdnsServiceType())) - .filter(s -> c.equals(s)).forEach(s -> suggestionFound(ServiceType.MDNS, c.getAddonId())))); - } - - /** - * Run a scheduled task to search for matching addon suggestion candidates using - * the UPnP service. - */ - @SuppressWarnings("unlikely-arg-type") - private void startScanUpnp() { - Future task = upnpScanTask; - if (task != null) { - task.cancel(false); - } - upnpScanTask = scheduler.submit(() -> upnpService.getRegistry().getRemoteDevices().forEach(d -> upnpCandidates - .stream().filter(c -> c.equals(d)).forEach(c -> suggestionFound(ServiceType.UPNP, c.getAddonId())))); - } + private void scanStart() { + finderTasks.forEach(t -> t.cancel(false)); + finderTasks.clear(); - /** - * Called back when a new addon suggestion is found. - * - * @param origin the service type that found the addon. - * @param addonUid the Uid of the found addon. - */ - private synchronized void suggestionFound(ServiceType origin, String addonUid) { - if (!suggestedAddonUids.contains(addonUid)) { - suggestedAddonUids.add(addonUid); - } - } + List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) + .flatMap(Collection::stream).toList(); - @Override - public void uninstall(String id) { - Future task = upnpScanTask; - if (task != null) { - task.cancel(true); - } - task = mdnsScanTask; - if (task != null) { - task.cancel(true); - } + finders.forEach(f -> { + f.setAddonCandidates(candidates); + finderTasks.add(scheduler.submit(() -> f.scanTask())); + }); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java deleted file mode 100644 index 31818156ac7..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/BaseCandidate.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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.config.discovery.addon.candidate; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * This is a {@link BaseCandidate} abstract base class for candidates - * for suggested addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public abstract class BaseCandidate { - protected final String addonId; - protected final Map propertyMatchRegexMap = new HashMap<>(); - - public BaseCandidate(String addonId, Map propertyMatchRegexMap) { - this.addonId = addonId; - this.propertyMatchRegexMap.putAll(propertyMatchRegexMap); - } - - public String getAddonId() { - return addonId; - } - - /** - * Helper method to check if the given property name is in the - * propertyMatchRegexMap and the given property value matches the respective - * regular expression. - * - * @param propertyName - * @param propertyValue - * @return true a) if the property name exists and the property value matches - * the regular expression, or b) the property name does not exist. - */ - protected boolean propertyMatches(String propertyName, String propertyValue) { - String matchRegex = propertyMatchRegexMap.get(propertyName); - return matchRegex == null ? true : propertyValue.matches(matchRegex); - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java deleted file mode 100644 index 5e56c88e344..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/MdnsCandidate.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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.config.discovery.addon.candidate; - -import java.util.Collections; -import java.util.Map; - -import javax.jmdns.ServiceInfo; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * This is a {@link MdnsCandidate} mdns version of class for candidates for - * suggested addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class MdnsCandidate extends BaseCandidate { - private final String mdnsServiceType; - - public MdnsCandidate(String bindingId, Map propertyMatchRegex, String serviceType) { - super(bindingId, propertyMatchRegex); - this.mdnsServiceType = serviceType; - } - - /** - * Check if the data in the provided ServiceInfo matches our own match criteria. - */ - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof ServiceInfo serviceInfo) { - return propertyMatches("application", serviceInfo.getApplication()) - && propertyMatches("name", serviceInfo.getName()) - && Collections.list(serviceInfo.getPropertyNames()).stream().allMatch( - propertyName -> propertyMatches(propertyName, serviceInfo.getPropertyString(propertyName))); - } - return false; - } - - public String getMdnsServiceType() { - return mdnsServiceType; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java deleted file mode 100644 index aa4b9433520..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/candidate/UpnpCandidate.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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.config.discovery.addon.candidate; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.jupnp.model.meta.RemoteDevice; - -/** - * This is a {@link UpnpCandidate} upnp version of class for candidates for - * suggested addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class UpnpCandidate extends BaseCandidate { - - public UpnpCandidate(String bindingId, Map propertyMatchRegexMap) { - super(bindingId, propertyMatchRegexMap); - } - - /** - * Check if the data in the provided RemoteDevice matches our own match - * criteria. - */ - @Override - public boolean equals(@Nullable Object object) { - if (object instanceof RemoteDevice device) { - return propertyMatches("deviceType", device.getType().getType()) - && propertyMatches("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) - && propertyMatches("modelName", device.getDetails().getModelDetails().getModelName()) - && propertyMatches("serialNumber", device.getDetails().getSerialNumber()) - && propertyMatches("udn", device.getIdentity().getUdn().getIdentifierString()); - } - return false; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java deleted file mode 100644 index bd38e68ba8c..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/Candidate.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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.config.discovery.addon.dto; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a single addon suggestion discovery candidate. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class Candidate { - private @Nullable String addonUid; - private @Nullable List discoveryMethods; - - public List getDiscoveryMethods() { - List discoveryMethods = this.discoveryMethods; - return discoveryMethods != null ? discoveryMethods : List.of(); - } - - public String getAddonUid() { - String addonUid = this.addonUid; - return addonUid != null ? addonUid : ""; - } - - public Candidate setDiscoveryMethods(List discoveryMethods) { - this.discoveryMethods = discoveryMethods; - return this; - } - - public Candidate setAddonUid(String addonUid) { - this.addonUid = addonUid; - return this; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java deleted file mode 100644 index 7e40eb8bee3..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/DiscoveryMethod.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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.config.discovery.addon.dto; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a single addon suggestion candidate discovery - * method. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class DiscoveryMethod { - private @Nullable String serviceType; - private @Nullable List matchProperties; - private @Nullable String mdnsServiceType; - - public ServiceType getServiceType() { - String serviceType = this.serviceType; - return ServiceType.valueOf(serviceType != null ? serviceType.toUpperCase() : ""); - } - - public String getMdnsServiceType() { - String mdnsServiceType = this.mdnsServiceType; - return mdnsServiceType != null ? mdnsServiceType : ""; - } - - public Map getMatchProperties() { - List matchProperties = this.matchProperties; - return matchProperties != null - ? matchProperties.stream().collect(Collectors.toMap(m -> m.getName(), m -> m.getRegex())) - : Map.of(); - } - - public DiscoveryMethod setServiceType(ServiceType serviceType) { - this.serviceType = serviceType.name().toLowerCase(); - return this; - } - - public DiscoveryMethod setMdnsServiceType(String mdnsServiceType) { - this.mdnsServiceType = mdnsServiceType; - return this; - } - - public DiscoveryMethod setMatchProperties(Map matchProperties) { - this.matchProperties = matchProperties.entrySet().stream().map(e -> new MatchProperty(e.getKey(), e.getValue())) - .toList(); - return this; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java deleted file mode 100644 index 5bd294fce1b..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/MatchProperty.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 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.config.discovery.addon.dto; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a property match regular expression. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class MatchProperty { - private @Nullable String name; - private @Nullable String regex; - - public MatchProperty(String name, String regex) { - this.name = name; - this.regex = regex; - } - - public String getName() { - String name = this.name; - return name != null ? name : ""; - } - - public String getRegex() { - String regex = this.regex; - return regex != null ? regex : ""; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java deleted file mode 100644 index c374f000cf6..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/dto/SuggestedAddonCandidates.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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.config.discovery.addon.dto; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a collection of potential suggested addon candidates. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class SuggestedAddonCandidates { - private @Nullable List candidates; - - public List getCandidates() { - List candidates = this.candidates; - return candidates != null ? candidates : List.of(); - } - - public SuggestedAddonCandidates setCandidates(List candidates) { - this.candidates = candidates; - return this; - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java new file mode 100644 index 00000000000..1b601d564b5 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -0,0 +1,73 @@ +/** + * 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.config.discovery.addon.finders; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.AddonInfo; + +/** + * This is a {@link AddonSuggestionFinder} abstract class for finding suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public abstract class AddonSuggestionFinder { + + protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); + protected final Set addonSuggestionUIDs = ConcurrentHashMap.newKeySet(); + protected boolean scanDone; + + public Set getAddonSuggestionUIDs() { + return addonSuggestionUIDs; + } + + /** + * Helper method to check if the given property name is in the propertyRegexMap + * and the given property value matches the respective regular expression. + * + * @param propertyRegexMap map of property names and regexes for value matching + * @param propertyName + * @param propertyValue + * @return true a) if the property name exists and the property value matches + * the regular expression, or b) the property name does not exist. + */ + protected static boolean propertyMatches(Map propertyRegexMap, String propertyName, + String propertyValue) { + String matchRegex = propertyRegexMap.get(propertyName); + return matchRegex == null ? true : propertyValue.matches(matchRegex); + } + + public void reset() { + addonCandidates.clear(); + addonSuggestionUIDs.clear(); + scanDone = false; + } + + public boolean scanDone() { + return scanDone; + } + + public abstract void scanTask(); + + public void setAddonCandidates(List candidates) { + reset(); + addonCandidates.addAll(candidates); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java new file mode 100644 index 00000000000..1305550368c --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -0,0 +1,87 @@ +/** + * 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.config.discovery.addon.finders; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.jmdns.ServiceEvent; +import javax.jmdns.ServiceListener; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryServiceType; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.io.transport.mdns.MDNSClient; + +/** + * This is a {@link MDNSAddonSuggestionFinder} for finding suggested addons via MDNS. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class MDNSAddonSuggestionFinder extends AddonSuggestionFinder { + + /** + * Anonymous ServiceListener implementation that ignores call-backs. + */ + private final ServiceListener noOp = new ServiceListener() { + + @Override + public void serviceAdded(@Nullable ServiceEvent event) { + } + + @Override + public void serviceRemoved(@Nullable ServiceEvent event) { + } + + @Override + public void serviceResolved(@Nullable ServiceEvent event) { + } + }; + + private final MDNSClient mdnsClient; + + public MDNSAddonSuggestionFinder(MDNSClient mdnsClient) { + this.mdnsClient = mdnsClient; + } + + @Override + public void scanTask() { + addonCandidates.forEach(c -> { + c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.MDNS == m.getServiceType()) + .forEach(m -> { + Map map = m.getPropertyRegexMap(); + Arrays.stream(mdnsClient.list(m.getMdnsServiceType())).forEach(s -> { + if (propertyMatches(map, "application", s.getApplication()) + && propertyMatches(map, "name", s.getName()) + && Collections.list(s.getPropertyNames()).stream() + .allMatch(n -> propertyMatches(map, n, s.getPropertyString(n)))) { + addonSuggestionUIDs.add(c.getUID()); + } + }); + }); + }); + scanDone = true; + } + + @Override + public void setAddonCandidates(List candidates) { + super.setAddonCandidates(candidates); + addonCandidates.forEach( + c -> c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.MDNS == m.getServiceType()) + .forEach(m -> mdnsClient.addServiceListener(m.getMdnsServiceType(), noOp))); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java new file mode 100644 index 00000000000..1424ba2aee5 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -0,0 +1,56 @@ +/** + * 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.config.discovery.addon.finders; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.jupnp.UpnpService; +import org.openhab.core.addon.AddonDiscoveryServiceType; + +/** + * This is a {@link UpnpAddonSuggestionFinder} for finding suggested addons via UPnP. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class UpnpAddonSuggestionFinder extends AddonSuggestionFinder { + + private final UpnpService upnpService; + + public UpnpAddonSuggestionFinder(UpnpService upnpService) { + this.upnpService = upnpService; + } + + @Override + public void scanTask() { + upnpService.getRegistry().getRemoteDevices().forEach(d -> { + addonCandidates.forEach(c -> { + c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.UPNP == m.getServiceType()) + .forEach(m -> { + Map map = m.getPropertyRegexMap(); + if (propertyMatches(map, "deviceType", d.getType().getType()) + && propertyMatches(map, "manufacturer", + d.getDetails().getManufacturerDetails().getManufacturer()) + && propertyMatches(map, "modelName", + d.getDetails().getModelDetails().getModelName()) + && propertyMatches(map, "serialNumber", d.getDetails().getSerialNumber()) + && propertyMatches(map, "udn", d.getIdentity().getUdn().getIdentifierString())) { + addonSuggestionUIDs.add(c.getUID()); + } + }); + }); + }); + scanDone = true; + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java deleted file mode 100644 index 7861638085b..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/xml/AddonCandidatesSerializer.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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.config.discovery.addon.xml; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.config.discovery.addon.dto.Candidate; -import org.openhab.core.config.discovery.addon.dto.DiscoveryMethod; -import org.openhab.core.config.discovery.addon.dto.MatchProperty; -import org.openhab.core.config.discovery.addon.dto.SuggestedAddonCandidates; - -import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.XStreamException; -import com.thoughtworks.xstream.io.xml.StaxDriver; - -/** - * Serializer/deserializer for suggested addon finder. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonCandidatesSerializer { - - private final XStream xStream; - - public AddonCandidatesSerializer() { - xStream = new XStream(new StaxDriver()); - xStream.alias("suggested-addon-candidates", SuggestedAddonCandidates.class); - xStream.addImplicitCollection(SuggestedAddonCandidates.class, "candidates"); - - xStream.alias("candidate", Candidate.class); - xStream.aliasField("addon-uid", Candidate.class, "addonUid"); - xStream.addImplicitCollection(Candidate.class, "discoveryMethods"); - - xStream.alias("discovery-method", DiscoveryMethod.class); - xStream.aliasField("service-type", DiscoveryMethod.class, "serviceType"); - xStream.aliasField("mdns-service-type", DiscoveryMethod.class, "mdnsServiceType"); - xStream.aliasField("match-properties", DiscoveryMethod.class, "matchProperties"); - xStream.addImplicitCollection(DiscoveryMethod.class, "matchProperties"); - - xStream.alias("match-property", MatchProperty.class); - xStream.allowTypesByWildcard(new String[] { "org.openhab.**" }); - } - - /** - * Deserialize the XML into a Candidates DTO. - * - * @param xml an XML serial image. - * @return a deserialized Candidates DTO. - * @throws XStreamException if unable to deserialize the XML. - */ - public SuggestedAddonCandidates fromXML(String xml) throws XStreamException { - return (SuggestedAddonCandidates) xStream.fromXML(xml); - } - - /** - * Serialize a Candidates DTO to XML. - * - * @param candidates the DTO to be serialized. - * @return an XML serial image of the DTO. - * @throws XStreamException if unable to serialize the DTO. - */ - public String toXML(SuggestedAddonCandidates candidates) throws XStreamException { - return xStream.toXML(candidates); - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml b/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml deleted file mode 100644 index e41f2c4de8c..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/resources/suggested-addon-candidates.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - binding.hpprinter - - mdns - - rp - .* - - - ty - hp (.*) - - _printer._tcp.local. - - - - binding.hue - - upnp - - modelName - Philips hue bridge - - - - mdns - _hue._tcp.local. - - - diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 6783c2dcabc..13ff530ea5a 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -191,7 +191,8 @@ public Response getSuggestions( @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); - return Response.ok(new Stream2JSONInputStream(addonSuggestionFinderService.getAddons(locale).stream())).build(); + return Response.ok(new Stream2JSONInputStream(addonSuggestionFinderService.getSuggestedAddons(locale).stream())) + .build(); } @GET diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 5cdcf866a1e..03edb0d66dd 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -66,6 +66,22 @@ mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest.sse/${project.version} + openhab-core-config-discovery-addon + + + + openhab-core-base + + openhab.tp;filter:="(feature=jmdns)" + openhab.tp-jmdns + + mvn:org.openhab.core.bundles/org.openhab.core.io.transport.mdns/${project.version} + + openhab.tp;filter:="(feature=jupnp)" + openhab.tp-jupnp + + mvn:org.openhab.core.bundles/org.openhab.core.addon/${project.version} + mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon/${project.version} diff --git a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java similarity index 68% rename from itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java rename to itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java index 02c75ab4b61..021dfe54cf7 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/SuggestedAddonFinderServiceOSGiTest.java +++ b/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java @@ -23,16 +23,17 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.IOException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import javax.jmdns.ServiceInfo; @@ -54,69 +55,105 @@ import org.jupnp.model.types.UDN; import org.mockito.Mockito; import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonDiscoveryServiceType; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonService; import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; +import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.core.test.java.JavaOSGiTest; -import com.thoughtworks.xstream.XStreamException; - /** - * Integration tests for the {@link SuggestedAddonFinderService}. + * Integration tests for the {@link AddonSuggestionFinderService}. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -public class SuggestedAddonFinderServiceOSGiTest extends JavaOSGiTest { +public class AddonSuggestionFinderServiceOSGiTest extends JavaOSGiTest { + private @NonNullByDefault({}) LocaleProvider localeProvider; private @NonNullByDefault({}) MDNSClient mdnsClient; private @NonNullByDefault({}) UpnpService upnpService; private @NonNullByDefault({}) AddonService addonService; + private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider; private @NonNullByDefault({}) AddonSuggestionFinderService addonSuggestionFinderService; @BeforeAll public void setup() { + setupMockLocaleProvider(); setupMockAddonService(); + setupMockAddonInfoProvider(); setupMockMdnsClient(); setupMockUpnpService(); createAddonSuggestionFinderService(); } private void createAddonSuggestionFinderService() { - // create the service - try { - addonSuggestionFinderService = new AddonSuggestionFinderService(mdnsClient, upnpService); - } catch (IOException e) { - fail("Error loading XML"); - } catch (XStreamException e) { - fail("Error parsing XML"); - } - addonSuggestionFinderService.addAddonService(addonService); - - // check that it exists + addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider, mdnsClient, upnpService); assertNotNull(addonSuggestionFinderService); + + addonSuggestionFinderService.addAddonService(addonService); } private void setupMockAddonService() { // create the mock addonService = mock(AddonService.class); List addons = new ArrayList<>(); - addons.add(Addon.create("binding.hue").withType("binding").withId("hue").build()); - addons.add(Addon.create("binding.hpprinter").withType("binding").withId("hpprinter").build()); + addons.add(Addon.create("binding-hue").withType("binding").withId("hue").build()); + addons.add(Addon.create("binding-hpprinter").withType("binding").withId("hpprinter").build()); when(addonService.getAddons(any(Locale.class))).thenReturn(addons); // check that it works assertNotNull(addonService); assertEquals(2, addonService.getAddons(Locale.US).size()); - assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding.hue".equals(a.getUid()))); - assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding.hpprinter".equals(a.getUid()))); + assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding-hue".equals(a.getUid()))); + assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding-hpprinter".equals(a.getUid()))); assertFalse(addonService.getAddons(Locale.US).stream().anyMatch(a -> "aardvark".equals(a.getUid()))); } + private void setupMockAddonInfoProvider() { + AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) + .setMatchProperties(Map.of("rp", ".*", "ty", "hp (.*)")).setMdnsServiceType("_printer._tcp.local."); + + AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.UPNP) + .setMatchProperties(Map.of("modelName", "Philips hue bridge")); + + AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) + .setMdnsServiceType("_hue._tcp.local."); + + // create the mock + addonInfoProvider = mock(AddonInfoProvider.class); + Set addonInfos = new HashSet<>(); + addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") + .withDiscoveryMethods(List.of(hue1, hue2)).build()); + addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer") + .withDiscoveryMethods(List.of(hp)).build()); + when(addonInfoProvider.getAddonInfos(any(Locale.class))).thenReturn(addonInfos); + + // check that it works + assertNotNull(addonInfoProvider); + Set addonInfos2 = addonInfoProvider.getAddonInfos(Locale.US); + assertEquals(2, addonInfos2.size()); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); + } + + private void setupMockLocaleProvider() { + // create the mock + localeProvider = mock(LocaleProvider.class); + when(localeProvider.getLocale()).thenReturn(Locale.US); + + // check that it works + assertNotNull(localeProvider); + assertEquals(Locale.US, localeProvider.getLocale()); + } + private void setupMockMdnsClient() { // create the mock - mdnsClient = mock(MDNSClient.class); + mdnsClient = mock(MDNSClient.class, Mockito.RETURNS_DEEP_STUBS); when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {}); ServiceInfo hueService = ServiceInfo.create("mdnsTest", "hue", 0, 0, 0, false, "hue service"); when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService }); @@ -180,15 +217,17 @@ private void setupMockUpnpService() { @Test public void testGetAddons() { + addonSuggestionFinderService.addAddonInfoProvider(addonInfoProvider); // give the scan tasks some time to complete try { Thread.sleep(100); } catch (InterruptedException e) { } assertTrue(addonSuggestionFinderService.scanDone()); - List addons = addonSuggestionFinderService.getAddons(Locale.US); + List addons = addonSuggestionFinderService.getSuggestedAddons(Locale.US); assertEquals(2, addons.size()); - assertTrue(addons.stream().anyMatch(a -> "binding.hue".equals(a.getUid()))); - assertTrue(addons.stream().anyMatch(a -> "binding.hpprinter".equals(a.getUid()))); + assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUid()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUid()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUid()))); } } From a993d6c45a3b714c44b4da6c49d3b42119d750d6 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 11 Oct 2023 22:18:40 +0100 Subject: [PATCH 20/98] [suggestion-finder] delete xsd file Signed-off-by: Andrew Fiddian-Green --- .../suggested-addon-candidates-1.0.0.xsd | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd diff --git a/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd deleted file mode 100644 index a719101b8fb..00000000000 --- a/bundles/org.openhab.core.addon/schema/suggested-addon-candidates-1.0.0.xsd +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 3f89f0312b515054b2e1c96fd53bada63d693f27 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 12 Oct 2023 09:27:17 +0100 Subject: [PATCH 21/98] [suggestion-finder] change osgi tst to junit test Signed-off-by: Andrew Fiddian-Green --- .../AddonSuggestionFinderServiceTests.java | 3 +- .../.classpath | 29 -------- .../.project | 23 ------ .../NOTICE | 14 ---- .../itest.bndrun | 71 ------------------- .../pom.xml | 16 ----- itests/pom.xml | 1 - 7 files changed, 1 insertion(+), 156 deletions(-) rename itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java => bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java (98%) delete mode 100644 itests/org.openhab.core.config.discovery.addon.tests/.classpath delete mode 100644 itests/org.openhab.core.config.discovery.addon.tests/.project delete mode 100644 itests/org.openhab.core.config.discovery.addon.tests/NOTICE delete mode 100644 itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun delete mode 100644 itests/org.openhab.core.config.discovery.addon.tests/pom.xml diff --git a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java similarity index 98% rename from itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java rename to bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 021dfe54cf7..0ee948f1503 100644 --- a/itests/org.openhab.core.config.discovery.addon.tests/src/main/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceOSGiTest.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -63,7 +63,6 @@ import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.io.transport.mdns.MDNSClient; -import org.openhab.core.test.java.JavaOSGiTest; /** * Integration tests for the {@link AddonSuggestionFinderService}. @@ -72,7 +71,7 @@ */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -public class AddonSuggestionFinderServiceOSGiTest extends JavaOSGiTest { +public class AddonSuggestionFinderServiceTests { private @NonNullByDefault({}) LocaleProvider localeProvider; private @NonNullByDefault({}) MDNSClient mdnsClient; diff --git a/itests/org.openhab.core.config.discovery.addon.tests/.classpath b/itests/org.openhab.core.config.discovery.addon.tests/.classpath deleted file mode 100644 index 9e55698cddc..00000000000 --- a/itests/org.openhab.core.config.discovery.addon.tests/.classpath +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/itests/org.openhab.core.config.discovery.addon.tests/.project b/itests/org.openhab.core.config.discovery.addon.tests/.project deleted file mode 100644 index d8eff8b2d91..00000000000 --- a/itests/org.openhab.core.config.discovery.addon.tests/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - org.openhab.core.config.discovery.addon.tests - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/itests/org.openhab.core.config.discovery.addon.tests/NOTICE b/itests/org.openhab.core.config.discovery.addon.tests/NOTICE deleted file mode 100644 index 6c17d0d8a45..00000000000 --- a/itests/org.openhab.core.config.discovery.addon.tests/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -This content is produced and maintained by the openHAB project. - -* Project home: https://www.openhab.org - -== Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Eclipse Public License 2.0 which is available at -https://www.eclipse.org/legal/epl-2.0/. - -== Source Code - -https://github.com/openhab/openhab-core - diff --git a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun b/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun deleted file mode 100644 index cd67d023171..00000000000 --- a/itests/org.openhab.core.config.discovery.addon.tests/itest.bndrun +++ /dev/null @@ -1,71 +0,0 @@ --include: ../itest-common.bndrun - -Bundle-SymbolicName: ${project.artifactId} -Fragment-Host: org.openhab.core.config.discovery.addon - --runrequires: bnd.identity;id='org.openhab.core.config.discovery.addon.tests' - -# -# done -# --runbundles: \ - org.eclipse.equinox.event;version='[1.4.300,1.4.301)',\ - org.osgi.service.event;version='[1.4.0,1.4.1)',\ - org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\ - org.hamcrest;version='[2.2.0,2.2.1)',\ - org.opentest4j;version='[1.2.0,1.2.1)',\ - com.sun.xml.bind.jaxb-osgi;version='[2.3.3,2.3.4)',\ - jakarta.xml.bind-api;version='[2.3.3,2.3.4)',\ - org.apache.servicemix.specs.activation-api-1.2.1;version='[1.2.1,1.2.2)',\ - org.glassfish.hk2.osgi-resource-locator;version='[1.0.3,1.0.4)',\ - jakarta.inject.jakarta.inject-api;version='[2.0.0,2.0.1)',\ - org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\ - si-units;version='[2.1.0,2.1.1)',\ - si.uom.si-quantity;version='[2.1.0,2.1.1)',\ - org.osgi.util.function;version='[1.2.0,1.2.1)',\ - org.osgi.util.promise;version='[1.2.0,1.2.1)',\ - ch.qos.logback.classic;version='[1.2.11,1.2.12)',\ - ch.qos.logback.core;version='[1.2.11,1.2.12)',\ - javax.jmdns;version='[3.5.8,3.5.9)',\ - biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\ - com.google.gson;version='[2.9.1,2.9.2)',\ - com.sun.jna;version='[5.12.1,5.12.2)',\ - org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ - org.apache.felix.http.servlet-api;version='[1.2.0,1.2.1)',\ - org.apache.felix.scr;version='[2.2.4,2.2.5)',\ - org.eclipse.jetty.http;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.io;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.security;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.server;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.servlet;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.util;version='[9.4.50,9.4.51)',\ - org.eclipse.jetty.util.ajax;version='[9.4.50,9.4.51)',\ - org.ops4j.pax.logging.pax-logging-api;version='[2.2.0,2.2.1)',\ - org.osgi.service.component;version='[1.5.0,1.5.1)',\ - xstream;version='[1.4.20,1.4.21)',\ - junit-jupiter-api;version='[5.9.2,5.9.3)',\ - junit-jupiter-engine;version='[5.9.2,5.9.3)',\ - junit-platform-commons;version='[1.9.2,1.9.3)',\ - junit-platform-engine;version='[1.9.2,1.9.3)',\ - junit-platform-launcher;version='[1.9.2,1.9.3)',\ - net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\ - net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\ - org.mockito.mockito-core;version='[4.11.0,4.11.1)',\ - org.objenesis;version='[3.3.0,3.3.1)',\ - org.openhab.core;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.core;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.discovery.addon;version='[4.1.0,4.1.1)',\ - org.openhab.core.config.discovery.addon.tests;version='[4.1.0,4.1.1)',\ - org.openhab.core.io.transport.mdns;version='[4.1.0,4.1.1)',\ - org.openhab.core.test;version='[4.1.0,4.1.1)',\ - org.openhab.base-fixes;version='[1.0.0,1.0.1)',\ - javax.measure.unit-api;version='[2.2.0,2.2.1)',\ - org.apiguardian.api;version='[1.1.2,1.1.3)',\ - tech.units.indriya;version='[2.2.0,2.2.1)',\ - uom-lib-common;version='[2.2.0,2.2.1)',\ - io.methvin.directory-watcher;version='[0.18.0,0.18.1)',\ - org.eclipse.jetty.client;version='[9.4.50,9.4.51)',\ - org.jupnp;version='[2.7.1,2.7.2)',\ - org.openhab.core.addon;version='[4.1.0,4.1.1)',\ - org.ops4j.pax.web.pax-web-api;version='[8.0.15,8.0.16)',\ - org.osgi.service.cm;version='[1.6.0,1.6.1)' diff --git a/itests/org.openhab.core.config.discovery.addon.tests/pom.xml b/itests/org.openhab.core.config.discovery.addon.tests/pom.xml deleted file mode 100644 index 2522a4a55fb..00000000000 --- a/itests/org.openhab.core.config.discovery.addon.tests/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - 4.0.0 - - - org.openhab.core.itests - org.openhab.core.reactor.itests - 4.1.0-SNAPSHOT - - - org.openhab.core.config.discovery.addon.tests - - openHAB Core :: Integration Tests :: Suggested Addon Finder Tests - - diff --git a/itests/pom.xml b/itests/pom.xml index 367ba10384d..f6884de2a79 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -26,7 +26,6 @@ org.openhab.core.automation.integration.tests org.openhab.core.addon.tests org.openhab.core.config.core.tests - org.openhab.core.config.discovery.addon.tests org.openhab.core.config.discovery.mdns.tests org.openhab.core.config.discovery.tests org.openhab.core.config.discovery.usbserial.tests From 85775fea709aafb62b5a337a9d08d3fa11bf38f8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 13 Oct 2023 19:25:03 +0100 Subject: [PATCH 22/98] [suggestion-finder] convert finders to osgi Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionFinderService.java | 63 +++++++++++-------- .../finders/MDNSAddonSuggestionFinder.java | 12 +++- .../finders/UpnpAddonSuggestionFinder.java | 12 +++- .../AddonSuggestionFinderServiceTests.java | 17 ++++- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 1ebc26d3b71..c9a883a9b1a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -25,17 +25,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.jupnp.UpnpService; import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonService; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.discovery.addon.finders.AddonSuggestionFinder; -import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; -import org.openhab.core.config.discovery.addon.finders.UpnpAddonSuggestionFinder; import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -53,21 +49,18 @@ @Component(immediate = true, service = AddonSuggestionFinderService.class, name = AddonSuggestionFinderService.SERVICE_NAME) public class AddonSuggestionFinderService implements AutoCloseable { - public static final String SERVICE_NAME = "suggested-addon-finder"; + public static final String SERVICE_NAME = "addon-suggestion-finder-service"; - private final Set addonServices = ConcurrentHashMap.newKeySet(); - private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); - private final List finders = Collections.synchronizedList(new ArrayList<>()); - private final List> finderTasks = Collections.synchronizedList(new ArrayList<>()); + private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); + private final Set addonServices = ConcurrentHashMap.newKeySet(); + private final List addonSuggestionFinders = Collections.synchronizedList(new ArrayList<>()); + private final List> addonSuggestionFinderTasks = Collections.synchronizedList(new ArrayList<>()); private final LocaleProvider localeProvider; @Activate - public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider, @Reference MDNSClient mdnsClient, - @Reference UpnpService upnpService) { + public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { this.localeProvider = localeProvider; - finders.add(new MDNSAddonSuggestionFinder(mdnsClient)); - finders.add(new UpnpAddonSuggestionFinder(upnpService)); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -79,7 +72,25 @@ public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { } public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - addonInfoProviders.remove(addonInfoProvider); + if (addonInfoProviders.contains(addonInfoProvider)) { + addonInfoProviders.remove(addonInfoProvider); + scanStart(); + } + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { + if (!addonSuggestionFinders.contains(addonSuggestionFinder)) { + addonSuggestionFinders.add(addonSuggestionFinder); + scanStart(); + } + } + + public void removeAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { + if (addonSuggestionFinders.contains(addonSuggestionFinder)) { + addonSuggestionFinders.remove(addonSuggestionFinder); + scanStart(); + } } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -94,34 +105,36 @@ public void removeAddonService(AddonService addonService) { @Deactivate @Override public void close() throws Exception { - finderTasks.forEach(t -> t.cancel(true)); - finderTasks.clear(); - finders.forEach(f -> f.reset()); - finders.clear(); + addonSuggestionFinderTasks.forEach(t -> t.cancel(true)); + addonSuggestionFinderTasks.clear(); + addonSuggestionFinders.forEach(f -> f.reset()); + addonSuggestionFinders.clear(); + addonInfoProviders.clear(); + addonServices.clear(); } public List getSuggestedAddons(@Nullable Locale locale) { - Set uids = finders.stream().map(f -> f.getAddonSuggestionUIDs()).flatMap(Collection::stream) - .collect(Collectors.toSet()); + Set uids = addonSuggestionFinders.stream().map(f -> f.getAddonSuggestionUIDs()) + .flatMap(Collection::stream).collect(Collectors.toSet()); return addonServices.stream().map(s -> s.getAddons(locale)).flatMap(Collection::stream) .filter(a -> uids.contains(a.getUid())).toList(); } public boolean scanDone() { - return finders.stream().allMatch(f -> f.scanDone()); + return addonSuggestionFinders.stream().allMatch(f -> f.scanDone()); } private void scanStart() { - finderTasks.forEach(t -> t.cancel(false)); - finderTasks.clear(); + addonSuggestionFinderTasks.forEach(t -> t.cancel(false)); + addonSuggestionFinderTasks.clear(); List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) .flatMap(Collection::stream).toList(); - finders.forEach(f -> { + addonSuggestionFinders.forEach(f -> { f.setAddonCandidates(candidates); - finderTasks.add(scheduler.submit(() -> f.scanTask())); + addonSuggestionFinderTasks.add(scheduler.submit(() -> f.scanTask())); }); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 1305550368c..b01ee2b3fae 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -25,15 +25,22 @@ import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.openhab.core.io.transport.mdns.MDNSClient; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** - * This is a {@link MDNSAddonSuggestionFinder} for finding suggested addons via MDNS. + * This is a {@link MDNSAddonSuggestionFinder} for finding suggested addons via + * MDNS. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault +@Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME) public class MDNSAddonSuggestionFinder extends AddonSuggestionFinder { + public static final String SERVICE_NAME = "mdns-addon-suggestion-finder"; + /** * Anonymous ServiceListener implementation that ignores call-backs. */ @@ -54,7 +61,8 @@ public void serviceResolved(@Nullable ServiceEvent event) { private final MDNSClient mdnsClient; - public MDNSAddonSuggestionFinder(MDNSClient mdnsClient) { + @Activate + public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 1424ba2aee5..5bb79b19fc0 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -17,18 +17,26 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.jupnp.UpnpService; import org.openhab.core.addon.AddonDiscoveryServiceType; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** - * This is a {@link UpnpAddonSuggestionFinder} for finding suggested addons via UPnP. + * This is a {@link UpnpAddonSuggestionFinder} for finding suggested addons via + * UPnP. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault +@Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME) public class UpnpAddonSuggestionFinder extends AddonSuggestionFinder { + public static final String SERVICE_NAME = "upnp-addon-suggestion-finder"; + private final UpnpService upnpService; - public UpnpAddonSuggestionFinder(UpnpService upnpService) { + @Activate + public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 0ee948f1503..5b97c13f7ac 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -39,6 +39,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -61,6 +62,8 @@ import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonService; import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; +import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; +import org.openhab.core.config.discovery.addon.finders.UpnpAddonSuggestionFinder; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.io.transport.mdns.MDNSClient; @@ -80,6 +83,16 @@ public class AddonSuggestionFinderServiceTests { private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider; private @NonNullByDefault({}) AddonSuggestionFinderService addonSuggestionFinderService; + @AfterAll + public void cleanUp() { + assertNotNull(addonSuggestionFinderService); + try { + addonSuggestionFinderService.close(); + } catch (Exception e) { + fail(e); + } + } + @BeforeAll public void setup() { setupMockLocaleProvider(); @@ -91,9 +104,11 @@ public void setup() { } private void createAddonSuggestionFinderService() { - addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider, mdnsClient, upnpService); + addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider); assertNotNull(addonSuggestionFinderService); + addonSuggestionFinderService.addAddonSuggestionFinder(new UpnpAddonSuggestionFinder(upnpService)); + addonSuggestionFinderService.addAddonSuggestionFinder(new MDNSAddonSuggestionFinder(mdnsClient)); addonSuggestionFinderService.addAddonService(addonService); } From 19d5058f08cc4095e290ab4a0b71c61a393cdc04 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 13 Oct 2023 20:00:44 +0100 Subject: [PATCH 23/98] [suggestion-finder] split interface from base class Signed-off-by: Andrew Fiddian-Green --- .../addon/finders/AddonSuggestionFinder.java | 50 ++----------- .../finders/BaseAddonSuggestionFinder.java | 73 +++++++++++++++++++ .../finders/MDNSAddonSuggestionFinder.java | 2 +- .../finders/UpnpAddonSuggestionFinder.java | 2 +- 4 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java index 1b601d564b5..9c876831b92 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -12,62 +12,28 @@ */ package org.openhab.core.config.discovery.addon.finders; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.addon.AddonInfo; /** - * This is a {@link AddonSuggestionFinder} abstract class for finding suggested addons. + * This is a {@link AddonSuggestionFinder} interface for finding suggested + * addons. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public abstract class AddonSuggestionFinder { +public interface AddonSuggestionFinder { - protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - protected final Set addonSuggestionUIDs = ConcurrentHashMap.newKeySet(); - protected boolean scanDone; + public Set getAddonSuggestionUIDs(); - public Set getAddonSuggestionUIDs() { - return addonSuggestionUIDs; - } + public void reset(); - /** - * Helper method to check if the given property name is in the propertyRegexMap - * and the given property value matches the respective regular expression. - * - * @param propertyRegexMap map of property names and regexes for value matching - * @param propertyName - * @param propertyValue - * @return true a) if the property name exists and the property value matches - * the regular expression, or b) the property name does not exist. - */ - protected static boolean propertyMatches(Map propertyRegexMap, String propertyName, - String propertyValue) { - String matchRegex = propertyRegexMap.get(propertyName); - return matchRegex == null ? true : propertyValue.matches(matchRegex); - } + public boolean scanDone(); - public void reset() { - addonCandidates.clear(); - addonSuggestionUIDs.clear(); - scanDone = false; - } + public void scanTask(); - public boolean scanDone() { - return scanDone; - } - - public abstract void scanTask(); - - public void setAddonCandidates(List candidates) { - reset(); - addonCandidates.addAll(candidates); - } + public void setAddonCandidates(List candidates); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java new file mode 100644 index 00000000000..410d2b54ec7 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -0,0 +1,73 @@ +/** + * 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.config.discovery.addon.finders; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.AddonInfo; + +/** + * This is a {@link BaseAddonSuggestionFinder} abstract class for finding suggested addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder { + + protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); + protected final Set addonSuggestionUIDs = ConcurrentHashMap.newKeySet(); + protected boolean scanDone; + + public Set getAddonSuggestionUIDs() { + return addonSuggestionUIDs; + } + + /** + * Helper method to check if the given property name is in the propertyRegexMap + * and the given property value matches the respective regular expression. + * + * @param propertyRegexMap map of property names and regexes for value matching + * @param propertyName + * @param propertyValue + * @return true a) if the property name exists and the property value matches + * the regular expression, or b) the property name does not exist. + */ + protected static boolean propertyMatches(Map propertyRegexMap, String propertyName, + String propertyValue) { + String matchRegex = propertyRegexMap.get(propertyName); + return matchRegex == null ? true : propertyValue.matches(matchRegex); + } + + public void reset() { + addonCandidates.clear(); + addonSuggestionUIDs.clear(); + scanDone = false; + } + + public boolean scanDone() { + return scanDone; + } + + public abstract void scanTask(); + + public void setAddonCandidates(List candidates) { + reset(); + addonCandidates.addAll(candidates); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index b01ee2b3fae..0f513d42970 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -37,7 +37,7 @@ */ @NonNullByDefault @Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME) -public class MDNSAddonSuggestionFinder extends AddonSuggestionFinder { +public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder { public static final String SERVICE_NAME = "mdns-addon-suggestion-finder"; diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 5bb79b19fc0..27cd1b58aa3 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -29,7 +29,7 @@ */ @NonNullByDefault @Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME) -public class UpnpAddonSuggestionFinder extends AddonSuggestionFinder { +public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder { public static final String SERVICE_NAME = "upnp-addon-suggestion-finder"; From 37bef623eac38f4efd00792eaf5d34d9233b8eed Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 14 Oct 2023 17:06:08 +0100 Subject: [PATCH 24/98] [suggestion-finder] add java doc; allow task cancel Signed-off-by: Andrew Fiddian-Green --- .../addon/finders/AddonSuggestionFinder.java | 41 +++++++++++++++++- .../finders/MDNSAddonSuggestionFinder.java | 37 +++++++++------- .../finders/UpnpAddonSuggestionFinder.java | 42 +++++++++++-------- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java index 9c876831b92..255c8bd7e2a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -19,21 +19,58 @@ import org.openhab.core.addon.AddonInfo; /** - * This is a {@link AddonSuggestionFinder} interface for finding suggested - * addons. + * This is a {@link AddonSuggestionFinder} interface for classes that find Addons that are suggested to be installed. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault public interface AddonSuggestionFinder { + /** + * The framework calls this method to get a set of Addon UID strings (e.g. 'binding-hue') relating to Addons that + * have been discovered via the {@code scanTask()} method. + *

+ * The result is only expected to be valid if {@code scanDone()} has returned true. + * + * @return a set of UID strings. + */ public Set getAddonSuggestionUIDs(); + /** + * The framework calls this method to reset the internal state of the finder. + */ public void reset(); + /** + * The framework calls this method to check if {@code scanTask()} has completed. + * + * @return true if {@code scanTask()} has completed. + */ public boolean scanDone(); + /** + * The framework calls this method on a scheduler thread e.g. via + * {@code Future task = scheduler.submit(() -> scanTask())} + *

+ * The task should scan through its candidate list of Addons, add those that it suggests to be installed to its + * AddonSuggestionUIDs set, and finally set the result of {@code scanDone()} to true when the task has completed. + *

+ * The task must be implemented so it can be externally cancelled via {@code task.cancel(true)} + */ public void scanTask(); + /** + * The framework calls this method to provide a list of AddonInfo elements which contain potential Addon candidates + * that this finder can iterate over in order to detect which ones to return via the getAddonSuggestionUIDs() + * method. + *

+ * It is expected that {@code getAddonSuggestionUIDs()} will return the UIDs of a filtered subset of the + * Addons provided in this candidate list. + *

+ * The framework will try to cancel any prior running {@code scanTask()} e.g. via {@code task.cancel(true)} before + * calling {@code setAddonCandidates()} and it will start a new {@code scanTask()} afterwards. + * + * @param candidates a list of AddonInfo candidates. + */ public void setAddonCandidates(List candidates); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 0f513d42970..f967839f60c 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -12,16 +12,17 @@ */ package org.openhab.core.config.discovery.addon.finders; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import javax.jmdns.ServiceEvent; +import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.openhab.core.io.transport.mdns.MDNSClient; @@ -68,20 +69,26 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { @Override public void scanTask() { - addonCandidates.forEach(c -> { - c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.MDNS == m.getServiceType()) - .forEach(m -> { - Map map = m.getPropertyRegexMap(); - Arrays.stream(mdnsClient.list(m.getMdnsServiceType())).forEach(s -> { - if (propertyMatches(map, "application", s.getApplication()) - && propertyMatches(map, "name", s.getName()) - && Collections.list(s.getPropertyNames()).stream() - .allMatch(n -> propertyMatches(map, n, s.getPropertyString(n)))) { - addonSuggestionUIDs.add(c.getUID()); - } - }); - }); - }); + for (AddonInfo candidate : addonCandidates) { + for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods()) { + if (AddonDiscoveryServiceType.MDNS != method.getServiceType()) { + continue; + } + Map map = method.getPropertyRegexMap(); + for (ServiceInfo service : mdnsClient.list(method.getMdnsServiceType())) { + if (Thread.interrupted()) { + // using nested for loops instead of forEach to allow external interruption + return; + } + if (propertyMatches(map, "application", service.getApplication()) + && propertyMatches(map, "name", service.getName()) + && Collections.list(service.getPropertyNames()).stream() + .allMatch(n -> propertyMatches(map, n, service.getPropertyString(n)))) { + addonSuggestionUIDs.add(candidate.getUID()); + } + } + } + } scanDone = true; } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 27cd1b58aa3..45e7bc04641 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -16,7 +16,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.jupnp.UpnpService; +import org.jupnp.model.meta.RemoteDevice; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonDiscoveryServiceType; +import org.openhab.core.addon.AddonInfo; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -42,23 +45,28 @@ public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { @Override public void scanTask() { - upnpService.getRegistry().getRemoteDevices().forEach(d -> { - addonCandidates.forEach(c -> { - c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.UPNP == m.getServiceType()) - .forEach(m -> { - Map map = m.getPropertyRegexMap(); - if (propertyMatches(map, "deviceType", d.getType().getType()) - && propertyMatches(map, "manufacturer", - d.getDetails().getManufacturerDetails().getManufacturer()) - && propertyMatches(map, "modelName", - d.getDetails().getModelDetails().getModelName()) - && propertyMatches(map, "serialNumber", d.getDetails().getSerialNumber()) - && propertyMatches(map, "udn", d.getIdentity().getUdn().getIdentifierString())) { - addonSuggestionUIDs.add(c.getUID()); - } - }); - }); - }); + for (RemoteDevice device : upnpService.getRegistry().getRemoteDevices()) { + for (AddonInfo candidate : addonCandidates) { + for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods()) { + if (Thread.interrupted()) { + // using nested for loops instead of forEach to allow external interruption + return; + } + if (AddonDiscoveryServiceType.UPNP != method.getServiceType()) { + continue; + } + Map map = method.getPropertyRegexMap(); + if (propertyMatches(map, "deviceType", device.getType().getType()) + && propertyMatches(map, "manufacturer", + device.getDetails().getManufacturerDetails().getManufacturer()) + && propertyMatches(map, "modelName", device.getDetails().getModelDetails().getModelName()) + && propertyMatches(map, "serialNumber", device.getDetails().getSerialNumber()) + && propertyMatches(map, "udn", device.getIdentity().getUdn().getIdentifierString())) { + addonSuggestionUIDs.add(candidate.getUID()); + } + } + } + } scanDone = true; } } From e2eeca6bee2000cc14371ca9b8d8b94725a972f1 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 14 Oct 2023 17:26:15 +0100 Subject: [PATCH 25/98] [suggestion-finder] java doc tweaks Signed-off-by: Andrew Fiddian-Green --- .../config/discovery/addon/AddonSuggestionFinderService.java | 3 +-- .../discovery/addon/finders/BaseAddonSuggestionFinder.java | 2 +- .../discovery/addon/finders/MDNSAddonSuggestionFinder.java | 3 +-- .../discovery/addon/finders/UpnpAddonSuggestionFinder.java | 3 +-- .../addon/tests/AddonSuggestionFinderServiceTests.java | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index c9a883a9b1a..a6af7bb12dd 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -40,8 +40,7 @@ import org.osgi.service.component.annotations.ReferencePolicy; /** - * This is a {@link AddonSuggestionFinderService} which discovers suggested - * addons for the user to install. + * This is a {@link AddonSuggestionFinderService} which discovers suggested Addons for the user to install. * * @author Andrew Fiddian-Green - Initial contribution */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index 410d2b54ec7..f93b0e2e223 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -23,7 +23,7 @@ import org.openhab.core.addon.AddonInfo; /** - * This is a {@link BaseAddonSuggestionFinder} abstract class for finding suggested addons. + * This is a {@link BaseAddonSuggestionFinder} abstract class for finding suggested Addons. * * @author Andrew Fiddian-Green - Initial contribution */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index f967839f60c..2d37687986e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -31,8 +31,7 @@ import org.osgi.service.component.annotations.Reference; /** - * This is a {@link MDNSAddonSuggestionFinder} for finding suggested addons via - * MDNS. + * This is a {@link MDNSAddonSuggestionFinder} for finding suggested Addons via MDNS. * * @author Andrew Fiddian-Green - Initial contribution */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 45e7bc04641..dec8650320e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -25,8 +25,7 @@ import org.osgi.service.component.annotations.Reference; /** - * This is a {@link UpnpAddonSuggestionFinder} for finding suggested addons via - * UPnP. + * This is a {@link UpnpAddonSuggestionFinder} for finding suggested Addons via UPnP. * * @author Andrew Fiddian-Green - Initial contribution */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 5b97c13f7ac..1374f2a1918 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -68,7 +68,7 @@ import org.openhab.core.io.transport.mdns.MDNSClient; /** - * Integration tests for the {@link AddonSuggestionFinderService}. + * JUnit tests for the {@link AddonSuggestionFinderService}. * * @author Andrew Fiddian-Green - Initial contribution */ From 735bdf592545d808d532797a180b7ab759c8ef51 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 17 Oct 2023 19:36:00 +0100 Subject: [PATCH 26/98] [suggestion-finder] xsd schema and dto Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-1.0.0.xsd | 79 +++++++++++-------- .../core/addon/AddonDiscoveryMethod.java | 2 +- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index c32e01a70e4..35dd0ccd5ca 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -7,15 +7,30 @@ - - - - - - - - - + + + + + + + + + + Comma-separated list of two-letter ISO country codes. + + + + + The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is <type>.<name> + + + + + + + + + Comma-separated list of two-letter ISO country codes. @@ -81,31 +96,31 @@ - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java index 264954f9c73..5d8a9ec26c3 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -27,8 +27,8 @@ @NonNullByDefault public class AddonDiscoveryMethod { private @Nullable String serviceType; - private @Nullable List matchProperties; private @Nullable String mdnsServiceType; + private @Nullable List matchProperties; public AddonDiscoveryServiceType getServiceType() { String serviceType = this.serviceType; From e98a4295a280e36e85805f2563cc061de3a7463d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 20 Oct 2023 18:11:37 +0100 Subject: [PATCH 27/98] [suggestion-finder] adapt for special addon Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 8 +++---- .../schema/addon-1.0.0.xsd | 22 +++++-------------- .../core/addon/AddonDiscoveryMethod.java | 13 ++++------- .../internal/xml/AddonInfoConverter.java | 20 +++++++++++++---- .../addon/internal/xml/AddonInfoReader.java | 5 +---- .../addon/AddonSuggestionFinderService.java | 14 ++++++++++++ .../finders/MDNSAddonSuggestionFinder.java | 4 +++- .../finders/UpnpAddonSuggestionFinder.java | 4 +++- .../AddonSuggestionFinderServiceTests.java | 8 +++++-- bundles/org.openhab.core.thing/.classpath | 18 ++++++++++++++- 10 files changed, 73 insertions(+), 43 deletions(-) diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index b5320e6c5b4..634118741ac 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -24,16 +24,16 @@ - + - - - + + + diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index 35dd0ccd5ca..de70f87a7e6 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -24,12 +24,12 @@ The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is <type>.<name> - - + + Comma-separated list of two-letter ISO country codes. @@ -96,23 +96,11 @@ - - - - - - - - - - - - - - - + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java index 5d8a9ec26c3..967cb868b11 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -13,8 +13,6 @@ package org.openhab.core.addon; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -40,11 +38,9 @@ public String getMdnsServiceType() { return mdnsServiceType != null ? mdnsServiceType : ""; } - public Map getPropertyRegexMap() { + public List getMatchProperties() { List matchProperties = this.matchProperties; - return matchProperties != null - ? matchProperties.stream().collect(Collectors.toMap(m -> m.getName(), m -> m.getRegex())) - : Map.of(); + return matchProperties != null ? matchProperties : List.of(); } public AddonDiscoveryMethod setServiceType(AddonDiscoveryServiceType serviceType) { @@ -57,9 +53,8 @@ public AddonDiscoveryMethod setMdnsServiceType(String mdnsServiceType) { return this; } - public AddonDiscoveryMethod setMatchProperties(Map matchProperties) { - this.matchProperties = matchProperties.entrySet().stream() - .map(e -> new AddonMatchProperty(e.getKey(), e.getValue())).toList(); + public AddonDiscoveryMethod setMatchProperties(List matchProperties) { + this.matchProperties = matchProperties; return this; } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java index 2e1db84409a..59f3941a100 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java @@ -13,6 +13,7 @@ package org.openhab.core.addon.internal.xml; import java.net.URI; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -109,10 +110,21 @@ public AddonInfoConverter() { addonInfo.withConfigDescriptionURI(configDescriptionURI); - Object object = nodeIterator.nextList("discovery-methods", false); - addonInfo.withDiscoveryMethods(!(object instanceof List list) ? null - : list.stream().filter(e -> (e instanceof AddonDiscoveryMethod)).map(e -> ((AddonDiscoveryMethod) e)) - .toList()); + List discoveryMethods = null; + while (true) { + Object value = nodeIterator.nextValue("discovery-method", false); + if (value instanceof AddonDiscoveryMethod discoveryMethod) { + if (discoveryMethods == null) { + discoveryMethods = new ArrayList<>(); + } + discoveryMethods.add(discoveryMethod); + } else { + break; + } + } + if (discoveryMethods != null) { + addonInfo.withDiscoveryMethods(discoveryMethods); + } nodeIterator.assertEndOfType(); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java index 47cad85e943..91026c9e248 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java @@ -90,12 +90,9 @@ protected void registerAliases(XStream xstream) { xstream.alias("filter", List.class); xstream.alias("criteria", FilterCriteria.class); xstream.alias("service-id", NodeValue.class); - xstream.alias("discovery-methods", NodeList.class); - xstream.alias("discovery-method", AddonDiscoveryMethod.class); xstream.alias("service-type", NodeValue.class); xstream.alias("mdns-service-type", NodeValue.class); - xstream.alias("match-properties", NodeList.class); + xstream.alias("discovery-method", AddonDiscoveryMethod.class); xstream.alias("match-property", AddonMatchProperty.class); - xstream.alias("regex", NodeValue.class); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index a6af7bb12dd..a50cf71657b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -49,6 +49,8 @@ public class AddonSuggestionFinderService implements AutoCloseable { public static final String SERVICE_NAME = "addon-suggestion-finder-service"; + public static final String DEFAULT_ADDON_SERVICE = "karaf"; + public static final String SUPPORTING_ADDON_ID = "addonsuggestionfinder"; private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); @@ -60,6 +62,7 @@ public class AddonSuggestionFinderService implements AutoCloseable { @Activate public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { this.localeProvider = localeProvider; + loadSupportingAddon(); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -120,6 +123,17 @@ public List getSuggestedAddons(@Nullable Locale locale) { .filter(a -> uids.contains(a.getUid())).toList(); } + /** + * Loads our special supporting addon 'org.openhab.misc.addonsuggestionfinder' that implements an AddonInfoProvider + * service that provides information about potential available addons that could be suggested to the user to be + * installed during setup. + */ + private void loadSupportingAddon() { + addonServices.stream().filter(s -> DEFAULT_ADDON_SERVICE.equals(s.getId())).findAny().ifPresent(s -> { + scheduler.submit(() -> s.install(SUPPORTING_ADDON_ID)); + }); + } + public boolean scanDone() { return addonSuggestionFinders.stream().allMatch(f -> f.scanDone()); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 2d37687986e..9823b74f274 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; @@ -73,7 +74,8 @@ public void scanTask() { if (AddonDiscoveryServiceType.MDNS != method.getServiceType()) { continue; } - Map map = method.getPropertyRegexMap(); + Map map = method.getMatchProperties().stream() + .collect(Collectors.toMap(e -> e.getName(), e -> e.getRegex())); for (ServiceInfo service : mdnsClient.list(method.getMdnsServiceType())) { if (Thread.interrupted()) { // using nested for loops instead of forEach to allow external interruption diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index dec8650320e..ee539708bc3 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -13,6 +13,7 @@ package org.openhab.core.config.discovery.addon.finders; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.jupnp.UpnpService; @@ -54,7 +55,8 @@ public void scanTask() { if (AddonDiscoveryServiceType.UPNP != method.getServiceType()) { continue; } - Map map = method.getPropertyRegexMap(); + Map map = method.getMatchProperties().stream() + .collect(Collectors.toMap(e -> e.getName(), e -> e.getRegex())); if (propertyMatches(map, "deviceType", device.getType().getType()) && propertyMatches(map, "manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 1374f2a1918..4102369b6f8 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -60,6 +60,7 @@ import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.addon.AddonService; import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; @@ -130,10 +131,12 @@ private void setupMockAddonService() { private void setupMockAddonInfoProvider() { AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) - .setMatchProperties(Map.of("rp", ".*", "ty", "hp (.*)")).setMdnsServiceType("_printer._tcp.local."); + .setMatchProperties( + List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)"))) + .setMdnsServiceType("_printer._tcp.local."); AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.UPNP) - .setMatchProperties(Map.of("modelName", "Philips hue bridge")); + .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) .setMdnsServiceType("_hue._tcp.local."); @@ -143,6 +146,7 @@ private void setupMockAddonInfoProvider() { Set addonInfos = new HashSet<>(); addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") .withDiscoveryMethods(List.of(hue1, hue2)).build()); + addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer") .withDiscoveryMethods(List.of(hp)).build()); when(addonInfoProvider.getAddonInfos(any(Locale.class))).thenReturn(addonInfos); diff --git a/bundles/org.openhab.core.thing/.classpath b/bundles/org.openhab.core.thing/.classpath index 5a77c00dbc1..50b5b3d2e8a 100644 --- a/bundles/org.openhab.core.thing/.classpath +++ b/bundles/org.openhab.core.thing/.classpath @@ -6,7 +6,12 @@ - + + + + + + @@ -31,5 +36,16 @@ + + + + + + + + + + + From 2eb632f800ebbdc01f83261bc94d0c200611e954 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 22 Oct 2023 00:30:51 +0100 Subject: [PATCH 28/98] [suggestion-finder] update xml schema, converters, and tests Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 7 ----- .../schema/addon-1.0.0.xsd | 16 ++++++++++-- .../core/addon/AddonDiscoveryMethod.java | 15 +++++------ .../core/addon/AddonDiscoveryServiceType.java | 26 ------------------- .../core/addon/AddonMatchProperty.java | 11 +++----- .../internal/xml/AddonInfoConverter.java | 20 +++----------- .../internal/xml/AddonInfoListConverter.java | 4 +-- .../internal/xml/AddonInfoListReader.java | 3 +-- .../addon/internal/xml/AddonInfoReader.java | 5 +++- .../org.openhab.core.config.core/.classpath | 12 ++++++++- .../core/xml/util/XmlDocumentReader.java | 2 ++ .../finders/BaseAddonSuggestionFinder.java | 3 +++ .../finders/MDNSAddonSuggestionFinder.java | 10 +++---- .../finders/UpnpAddonSuggestionFinder.java | 6 ++--- .../AddonSuggestionFinderServiceTests.java | 7 +++-- .../core/addon/xml/test/AddonInfoTest.java | 2 -- 16 files changed, 63 insertions(+), 86 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index 634118741ac..e591f47ba53 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -29,12 +29,5 @@ - - - - - - - diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index de70f87a7e6..c4ef068ca81 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -28,7 +28,7 @@ - + @@ -96,11 +96,23 @@ + + + + + + - + + + + + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java index 967cb868b11..2e923a2f965 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -24,13 +24,12 @@ */ @NonNullByDefault public class AddonDiscoveryMethod { - private @Nullable String serviceType; + private @NonNullByDefault({}) String serviceType; private @Nullable String mdnsServiceType; private @Nullable List matchProperties; - public AddonDiscoveryServiceType getServiceType() { - String serviceType = this.serviceType; - return AddonDiscoveryServiceType.valueOf(serviceType != null ? serviceType.toUpperCase() : ""); + public String getServiceType() { + return serviceType.toLowerCase(); } public String getMdnsServiceType() { @@ -43,17 +42,17 @@ public List getMatchProperties() { return matchProperties != null ? matchProperties : List.of(); } - public AddonDiscoveryMethod setServiceType(AddonDiscoveryServiceType serviceType) { - this.serviceType = serviceType.name().toLowerCase(); + public AddonDiscoveryMethod setServiceType(String serviceType) { + this.serviceType = serviceType.toLowerCase(); return this; } - public AddonDiscoveryMethod setMdnsServiceType(String mdnsServiceType) { + public AddonDiscoveryMethod setMdnsServiceType(@Nullable String mdnsServiceType) { this.mdnsServiceType = mdnsServiceType; return this; } - public AddonDiscoveryMethod setMatchProperties(List matchProperties) { + public AddonDiscoveryMethod setMatchProperties(@Nullable List matchProperties) { this.matchProperties = matchProperties; return this; } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java deleted file mode 100644 index a9526b50246..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryServiceType.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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.addon; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * Enum of different supported discovery types. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public enum AddonDiscoveryServiceType { - MDNS, - UPNP -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java index 06943b64fa5..c85040a535b 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -13,7 +13,6 @@ package org.openhab.core.addon; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; /** * DTO for serialization of a property match regular expression. @@ -22,8 +21,8 @@ */ @NonNullByDefault public class AddonMatchProperty { - private @Nullable String name; - private @Nullable String regex; + private @NonNullByDefault({}) String name; + private @NonNullByDefault({}) String regex; public AddonMatchProperty(String name, String regex) { this.name = name; @@ -31,12 +30,10 @@ public AddonMatchProperty(String name, String regex) { } public String getName() { - String name = this.name; - return name != null ? name : ""; + return name; } public String getRegex() { - String regex = this.regex; - return regex != null ? regex : ""; + return regex; } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java index 59f3941a100..2e1db84409a 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java @@ -13,7 +13,6 @@ package org.openhab.core.addon.internal.xml; import java.net.URI; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -110,21 +109,10 @@ public AddonInfoConverter() { addonInfo.withConfigDescriptionURI(configDescriptionURI); - List discoveryMethods = null; - while (true) { - Object value = nodeIterator.nextValue("discovery-method", false); - if (value instanceof AddonDiscoveryMethod discoveryMethod) { - if (discoveryMethods == null) { - discoveryMethods = new ArrayList<>(); - } - discoveryMethods.add(discoveryMethod); - } else { - break; - } - } - if (discoveryMethods != null) { - addonInfo.withDiscoveryMethods(discoveryMethods); - } + Object object = nodeIterator.nextList("discovery-methods", false); + addonInfo.withDiscoveryMethods(!(object instanceof List list) ? null + : list.stream().filter(e -> (e instanceof AddonDiscoveryMethod)).map(e -> ((AddonDiscoveryMethod) e)) + .toList()); nodeIterator.assertEndOfType(); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java index 60a93ad0027..9f745f46bae 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java @@ -46,8 +46,8 @@ public AddonInfoListConverter() { Object object = nodeIterator.nextList("addons", false); List addons = (object instanceof List list) - ? list.stream().filter(e -> e != null).filter(e -> (e instanceof AddonInfoXmlResult)) - .map(e -> (AddonInfoXmlResult) e).map(r -> r.addonInfo()).toList() + ? list.stream().filter(e -> (e instanceof AddonInfoXmlResult)).map(e -> (AddonInfoXmlResult) e) + .map(r -> r.addonInfo()).toList() : null; nodeIterator.assertEndOfType(); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java index a733e169492..28819af791d 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java @@ -37,7 +37,7 @@ import com.thoughtworks.xstream.XStream; /** - * The {@link AddonInfoListReader} reads XML documents, which contain the {@code addon} XML tag, and converts them to + * The {@link AddonInfoListReader} reads XML documents, which contain the {@code binding} XML tag, and converts them to * a List of {@link AddonInfoXmlResult} objects. *

* This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document. @@ -74,7 +74,6 @@ protected void registerConverters(XStream xstream) { @Override protected void registerAliases(XStream xstream) { - xstream.alias("addon-info-list", AddonInfoList.class); xstream.alias("addons", NodeList.class); xstream.alias("addon", AddonInfoXmlResult.class); xstream.alias("name", NodeValue.class); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java index 91026c9e248..47cad85e943 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java @@ -90,9 +90,12 @@ protected void registerAliases(XStream xstream) { xstream.alias("filter", List.class); xstream.alias("criteria", FilterCriteria.class); xstream.alias("service-id", NodeValue.class); + xstream.alias("discovery-methods", NodeList.class); + xstream.alias("discovery-method", AddonDiscoveryMethod.class); xstream.alias("service-type", NodeValue.class); xstream.alias("mdns-service-type", NodeValue.class); - xstream.alias("discovery-method", AddonDiscoveryMethod.class); + xstream.alias("match-properties", NodeList.class); xstream.alias("match-property", AddonMatchProperty.class); + xstream.alias("regex", NodeValue.class); } } diff --git a/bundles/org.openhab.core.config.core/.classpath b/bundles/org.openhab.core.config.core/.classpath index 5a77c00dbc1..ae05ef201a9 100644 --- a/bundles/org.openhab.core.config.core/.classpath +++ b/bundles/org.openhab.core.config.core/.classpath @@ -6,7 +6,12 @@ - + + + + + + @@ -31,5 +36,10 @@ + + + + + diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java index 5c2754c6f6f..a413fcd9930 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java @@ -109,6 +109,8 @@ protected void configureSecurity(XStream xstream) { /** * Reads the XML document containing a specific XML tag from the specified xml string and converts it to the * according object. + *

+ * This method returns {@code null} if the given URL is {@code null}. * * @param xml a string containing the XML document to be read. * @return the conversion result object (could be null). diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index f93b0e2e223..dbc07c1a60b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -30,8 +30,11 @@ @NonNullByDefault public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder { + protected static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; + protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); protected final Set addonSuggestionUIDs = ConcurrentHashMap.newKeySet(); + protected boolean scanDone; public Set getAddonSuggestionUIDs() { diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 9823b74f274..3618167c252 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -24,7 +24,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; @@ -40,7 +39,8 @@ @Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME) public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder { - public static final String SERVICE_NAME = "mdns-addon-suggestion-finder"; + public static final String SERVICE_TYPE = "mdns"; + public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; /** * Anonymous ServiceListener implementation that ignores call-backs. @@ -71,7 +71,7 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { public void scanTask() { for (AddonInfo candidate : addonCandidates) { for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods()) { - if (AddonDiscoveryServiceType.MDNS != method.getServiceType()) { + if (!SERVICE_TYPE.equals(method.getServiceType())) { continue; } Map map = method.getMatchProperties().stream() @@ -96,8 +96,8 @@ && propertyMatches(map, "name", service.getName()) @Override public void setAddonCandidates(List candidates) { super.setAddonCandidates(candidates); - addonCandidates.forEach( - c -> c.getDiscoveryMethods().stream().filter(m -> AddonDiscoveryServiceType.MDNS == m.getServiceType()) + addonCandidates + .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) .forEach(m -> mdnsClient.addServiceListener(m.getMdnsServiceType(), noOp))); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index ee539708bc3..fcce5ef2e3e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -19,7 +19,6 @@ import org.jupnp.UpnpService; import org.jupnp.model.meta.RemoteDevice; import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -34,7 +33,8 @@ @Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME) public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder { - public static final String SERVICE_NAME = "upnp-addon-suggestion-finder"; + public static final String SERVICE_TYPE = "upnp"; + public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; private final UpnpService upnpService; @@ -52,7 +52,7 @@ public void scanTask() { // using nested for loops instead of forEach to allow external interruption return; } - if (AddonDiscoveryServiceType.UPNP != method.getServiceType()) { + if (!SERVICE_TYPE.equals(method.getServiceType())) { continue; } Map map = method.getMatchProperties().stream() diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 4102369b6f8..7a6c0329776 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -57,7 +57,6 @@ import org.mockito.Mockito; import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonDiscoveryServiceType; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonMatchProperty; @@ -130,15 +129,15 @@ private void setupMockAddonService() { } private void setupMockAddonInfoProvider() { - AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) + AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(MDNSAddonSuggestionFinder.SERVICE_TYPE) .setMatchProperties( List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)"))) .setMdnsServiceType("_printer._tcp.local."); - AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.UPNP) + AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(UpnpAddonSuggestionFinder.SERVICE_TYPE) .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); - AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(AddonDiscoveryServiceType.MDNS) + AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(MDNSAddonSuggestionFinder.SERVICE_TYPE) .setMdnsServiceType("_hue._tcp.local."); // create the mock diff --git a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java index 3e109b7a5a9..62a49060580 100644 --- a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java +++ b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java @@ -17,7 +17,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.List; @@ -96,7 +95,6 @@ public void assertThatAddonInfoIsReadProperly() throws Exception { assertNotNull(property); assertEquals("modelName", property.getName()); assertEquals("Philips hue bridge", property.getRegex()); - assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); }); } From dd4c29a020632406c98e936933701cc400526b9c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 23 Oct 2023 13:57:31 +0100 Subject: [PATCH 29/98] [suggested addon finder] refactoring Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 7 +++++++ .../addon/{internal/xml => }/AddonInfoListReader.java | 11 +++++++---- .../addon/internal/xml/AddonInfoListConverter.java | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) rename bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/{internal/xml => }/AddonInfoListReader.java (91%) diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index e591f47ba53..634118741ac 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -29,5 +29,12 @@ + + + + + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java similarity index 91% rename from bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java rename to bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java index 28819af791d..393444579c1 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java @@ -10,14 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.addon.internal.xml; +package org.openhab.core.addon; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.addon.internal.xml.AddonDiscoveryMethodConverter; +import org.openhab.core.addon.internal.xml.AddonInfoConverter; +import org.openhab.core.addon.internal.xml.AddonInfoListConverter; +import org.openhab.core.addon.internal.xml.AddonInfoXmlResult; +import org.openhab.core.addon.internal.xml.AddonMatchPropertyConverter; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; @@ -74,6 +76,7 @@ protected void registerConverters(XStream xstream) { @Override protected void registerAliases(XStream xstream) { + xstream.alias("addon-info-list", AddonInfoList.class); xstream.alias("addons", NodeList.class); xstream.alias("addon", AddonInfoXmlResult.class); xstream.alias("name", NodeValue.class); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java index 9f745f46bae..60a93ad0027 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java @@ -46,8 +46,8 @@ public AddonInfoListConverter() { Object object = nodeIterator.nextList("addons", false); List addons = (object instanceof List list) - ? list.stream().filter(e -> (e instanceof AddonInfoXmlResult)).map(e -> (AddonInfoXmlResult) e) - .map(r -> r.addonInfo()).toList() + ? list.stream().filter(e -> e != null).filter(e -> (e instanceof AddonInfoXmlResult)) + .map(e -> (AddonInfoXmlResult) e).map(r -> r.addonInfo()).toList() : null; nodeIterator.assertEndOfType(); From 753aa2e83eb833bc31e516482d7efe94646eae4e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 25 Oct 2023 22:42:04 +0100 Subject: [PATCH 30/98] [suggested addon finder] refactoring Signed-off-by: Andrew Fiddian-Green --- .../core/addon/AddonMatchProperty.java | 10 +- .../.classpath | 5 - .../addon/AddonSuggestionFinderService.java | 87 ++--------- .../addon/finders/AddonSuggestionFinder.java | 45 +----- .../finders/BaseAddonSuggestionFinder.java | 36 ++--- .../finders/MDNSAddonSuggestionFinder.java | 108 +++++++------ .../finders/UpnpAddonSuggestionFinder.java | 144 ++++++++++++++---- .../AddonSuggestionFinderServiceTests.java | 63 +++----- bundles/org.openhab.core.thing/.classpath | 51 ------- 9 files changed, 238 insertions(+), 311 deletions(-) delete mode 100644 bundles/org.openhab.core.thing/.classpath diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java index c85040a535b..df16ef89dbd 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -12,6 +12,8 @@ */ package org.openhab.core.addon; +import java.util.regex.Pattern; + import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -22,18 +24,18 @@ @NonNullByDefault public class AddonMatchProperty { private @NonNullByDefault({}) String name; - private @NonNullByDefault({}) String regex; + private @NonNullByDefault({}) Pattern pattern; public AddonMatchProperty(String name, String regex) { this.name = name; - this.regex = regex; + this.pattern = Pattern.compile(regex); } public String getName() { return name; } - public String getRegex() { - return regex; + public Pattern getPattern() { + return this.pattern; } } diff --git a/bundles/org.openhab.core.config.discovery.addon/.classpath b/bundles/org.openhab.core.config.discovery.addon/.classpath index 8f89649a811..e9d63b05acb 100644 --- a/bundles/org.openhab.core.config.discovery.addon/.classpath +++ b/bundles/org.openhab.core.config.discovery.addon/.classpath @@ -25,10 +25,5 @@ - - - - - diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index a50cf71657b..3cd133daca7 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -19,19 +19,13 @@ import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonService; -import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.discovery.addon.finders.AddonSuggestionFinder; -import org.openhab.core.i18n.LocaleProvider; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -49,105 +43,44 @@ public class AddonSuggestionFinderService implements AutoCloseable { public static final String SERVICE_NAME = "addon-suggestion-finder-service"; - public static final String DEFAULT_ADDON_SERVICE = "karaf"; - public static final String SUPPORTING_ADDON_ID = "addonsuggestionfinder"; - private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); - private final Set addonServices = ConcurrentHashMap.newKeySet(); private final List addonSuggestionFinders = Collections.synchronizedList(new ArrayList<>()); - private final List> addonSuggestionFinderTasks = Collections.synchronizedList(new ArrayList<>()); - private final LocaleProvider localeProvider; @Activate - public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { - this.localeProvider = localeProvider; - loadSupportingAddon(); + public AddonSuggestionFinderService() { } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - if (!addonInfoProviders.contains(addonInfoProvider)) { - addonInfoProviders.add(addonInfoProvider); - scanStart(); - } + addonInfoProviders.add(addonInfoProvider); } public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - if (addonInfoProviders.contains(addonInfoProvider)) { - addonInfoProviders.remove(addonInfoProvider); - scanStart(); - } + addonInfoProviders.remove(addonInfoProvider); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { - if (!addonSuggestionFinders.contains(addonSuggestionFinder)) { - addonSuggestionFinders.add(addonSuggestionFinder); - scanStart(); - } + addonSuggestionFinders.add(addonSuggestionFinder); } public void removeAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { - if (addonSuggestionFinders.contains(addonSuggestionFinder)) { - addonSuggestionFinders.remove(addonSuggestionFinder); - scanStart(); - } - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonService(AddonService addonService) { - addonServices.add(addonService); - } - - public void removeAddonService(AddonService addonService) { - addonServices.remove(addonService); + addonSuggestionFinders.remove(addonSuggestionFinder); } @Deactivate @Override public void close() throws Exception { - addonSuggestionFinderTasks.forEach(t -> t.cancel(true)); - addonSuggestionFinderTasks.clear(); - addonSuggestionFinders.forEach(f -> f.reset()); addonSuggestionFinders.clear(); addonInfoProviders.clear(); - addonServices.clear(); - } - - public List getSuggestedAddons(@Nullable Locale locale) { - Set uids = addonSuggestionFinders.stream().map(f -> f.getAddonSuggestionUIDs()) - .flatMap(Collection::stream).collect(Collectors.toSet()); - - return addonServices.stream().map(s -> s.getAddons(locale)).flatMap(Collection::stream) - .filter(a -> uids.contains(a.getUid())).toList(); - } - - /** - * Loads our special supporting addon 'org.openhab.misc.addonsuggestionfinder' that implements an AddonInfoProvider - * service that provides information about potential available addons that could be suggested to the user to be - * installed during setup. - */ - private void loadSupportingAddon() { - addonServices.stream().filter(s -> DEFAULT_ADDON_SERVICE.equals(s.getId())).findAny().ifPresent(s -> { - scheduler.submit(() -> s.install(SUPPORTING_ADDON_ID)); - }); } - public boolean scanDone() { - return addonSuggestionFinders.stream().allMatch(f -> f.scanDone()); - } - - private void scanStart() { - addonSuggestionFinderTasks.forEach(t -> t.cancel(false)); - addonSuggestionFinderTasks.clear(); - - List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) + public Set getSuggestedAddons(@Nullable Locale locale) { + List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(locale)) .flatMap(Collection::stream).toList(); - - addonSuggestionFinders.forEach(f -> { - f.setAddonCandidates(candidates); - addonSuggestionFinderTasks.add(scheduler.submit(() -> f.scanTask())); - }); + addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); + return addonSuggestionFinders.stream().map(f -> f.getSuggestedAddons()).flatMap(Collection::stream) + .collect(Collectors.toSet()); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java index 255c8bd7e2a..2e8809ce3e7 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -27,48 +27,15 @@ public interface AddonSuggestionFinder { /** - * The framework calls this method to get a set of Addon UID strings (e.g. 'binding-hue') relating to Addons that - * have been discovered via the {@code scanTask()} method. - *

- * The result is only expected to be valid if {@code scanDone()} has returned true. - * - * @return a set of UID strings. - */ - public Set getAddonSuggestionUIDs(); - - /** - * The framework calls this method to reset the internal state of the finder. - */ - public void reset(); - - /** - * The framework calls this method to check if {@code scanTask()} has completed. - * - * @return true if {@code scanTask()} has completed. - */ - public boolean scanDone(); - - /** - * The framework calls this method on a scheduler thread e.g. via - * {@code Future task = scheduler.submit(() -> scanTask())} - *

- * The task should scan through its candidate list of Addons, add those that it suggests to be installed to its - * AddonSuggestionUIDs set, and finally set the result of {@code scanDone()} to true when the task has completed. - *

- * The task must be implemented so it can be externally cancelled via {@code task.cancel(true)} + * The framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of + * those that it suggests to be installed. */ - public void scanTask(); + public Set getSuggestedAddons(); /** - * The framework calls this method to provide a list of AddonInfo elements which contain potential Addon candidates - * that this finder can iterate over in order to detect which ones to return via the getAddonSuggestionUIDs() - * method. - *

- * It is expected that {@code getAddonSuggestionUIDs()} will return the UIDs of a filtered subset of the - * Addons provided in this candidate list. - *

- * The framework will try to cancel any prior running {@code scanTask()} e.g. via {@code task.cancel(true)} before - * calling {@code setAddonCandidates()} and it will start a new {@code scanTask()} afterwards. + * The framework calls this method to provide a list of {@link AddonInfo} elements which contain potential + * candidates that this finder can iterate over in order to detect which ones to return via the + * {@code getSuggestedAddons()} method. * * @param candidates a list of AddonInfo candidates. */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index dbc07c1a60b..121fdda4104 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -17,7 +17,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.addon.AddonInfo; @@ -32,45 +32,31 @@ public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder protected static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; - protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - protected final Set addonSuggestionUIDs = ConcurrentHashMap.newKeySet(); - - protected boolean scanDone; - - public Set getAddonSuggestionUIDs() { - return addonSuggestionUIDs; - } - /** - * Helper method to check if the given property name is in the propertyRegexMap - * and the given property value matches the respective regular expression. + * Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the + * given {@code propertyValue} matches the respective regular expression {@code Pattern}. * - * @param propertyRegexMap map of property names and regexes for value matching + * @param propertyPatternMap map of property names and regex patterns for value matching * @param propertyName * @param propertyValue * @return true a) if the property name exists and the property value matches * the regular expression, or b) the property name does not exist. */ - protected static boolean propertyMatches(Map propertyRegexMap, String propertyName, + protected static boolean propertyMatches(Map propertyPatternMap, String propertyName, String propertyValue) { - String matchRegex = propertyRegexMap.get(propertyName); - return matchRegex == null ? true : propertyValue.matches(matchRegex); + Pattern pattern = propertyPatternMap.get(propertyName); + return pattern == null ? true : pattern.matcher(propertyValue).matches(); } - public void reset() { - addonCandidates.clear(); - addonSuggestionUIDs.clear(); - scanDone = false; - } + protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - public boolean scanDone() { - return scanDone; + public void close() { + addonCandidates.clear(); } - public abstract void scanTask(); + public abstract Set getSuggestedAddons(); public void setAddonCandidates(List candidates) { - reset(); addonCandidates.addAll(candidates); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 3618167c252..9bf8c21bc76 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -13,8 +13,12 @@ package org.openhab.core.config.discovery.addon.finders; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.jmdns.ServiceEvent; @@ -23,12 +27,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This is a {@link MDNSAddonSuggestionFinder} for finding suggested Addons via MDNS. @@ -37,29 +43,16 @@ */ @NonNullByDefault @Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME) -public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder { +public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder implements ServiceListener { public static final String SERVICE_TYPE = "mdns"; public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; - /** - * Anonymous ServiceListener implementation that ignores call-backs. - */ - private final ServiceListener noOp = new ServiceListener() { - - @Override - public void serviceAdded(@Nullable ServiceEvent event) { - } - - @Override - public void serviceRemoved(@Nullable ServiceEvent event) { - } - - @Override - public void serviceResolved(@Nullable ServiceEvent event) { - } - }; + private static final String APPLICATION = "application"; + private static final String NAME = "name"; + private final Logger logger = LoggerFactory.getLogger(MDNSAddonSuggestionFinder.class); + private final Set services = ConcurrentHashMap.newKeySet(); private final MDNSClient mdnsClient; @Activate @@ -67,37 +60,64 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } + public void addService(@Nullable ServiceInfo service) { + if (service != null) { + services.add(service); + } + } + + @Deactivate + public void close() { + services.clear(); + super.close(); + } + + @Override + public Set getSuggestedAddons() { + Set result = new HashSet<>(); + addonCandidates.forEach(candidate -> { + candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) + .forEach(method -> { + Map map = method.getMatchProperties().stream().collect( + Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + + services.stream().forEach(service -> { + if (method.getMdnsServiceType().equals(service.getType()) + && propertyMatches(map, APPLICATION, service.getApplication()) + && propertyMatches(map, NAME, service.getName()) + && Collections.list(service.getPropertyNames()).stream().allMatch( + name -> propertyMatches(map, name, service.getPropertyString(name)))) { + result.add(candidate); + logger.debug("Addon '{}' will be suggested", candidate.getUID()); + } + }); + }); + }); + return result; + } + + @Override + public void serviceAdded(@Nullable ServiceEvent event) { + if (event != null) { + addService(event.getInfo()); + } + } + + @Override + public void serviceRemoved(@Nullable ServiceEvent event) { + } + @Override - public void scanTask() { - for (AddonInfo candidate : addonCandidates) { - for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods()) { - if (!SERVICE_TYPE.equals(method.getServiceType())) { - continue; - } - Map map = method.getMatchProperties().stream() - .collect(Collectors.toMap(e -> e.getName(), e -> e.getRegex())); - for (ServiceInfo service : mdnsClient.list(method.getMdnsServiceType())) { - if (Thread.interrupted()) { - // using nested for loops instead of forEach to allow external interruption - return; - } - if (propertyMatches(map, "application", service.getApplication()) - && propertyMatches(map, "name", service.getName()) - && Collections.list(service.getPropertyNames()).stream() - .allMatch(n -> propertyMatches(map, n, service.getPropertyString(n)))) { - addonSuggestionUIDs.add(candidate.getUID()); - } - } - } + public void serviceResolved(@Nullable ServiceEvent event) { + if (event != null) { + addService(event.getInfo()); } - scanDone = true; } @Override public void setAddonCandidates(List candidates) { super.setAddonCandidates(candidates); - addonCandidates - .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) - .forEach(m -> mdnsClient.addServiceListener(m.getMdnsServiceType(), noOp))); + candidates.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) + .forEach(m -> mdnsClient.addServiceListener(m.getMdnsServiceType(), this))); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index fcce5ef2e3e..6b64ccef7c4 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -12,17 +12,30 @@ */ package org.openhab.core.config.discovery.addon.finders; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.jupnp.UpnpService; +import org.jupnp.model.meta.DeviceDetails; +import org.jupnp.model.meta.LocalDevice; +import org.jupnp.model.meta.ManufacturerDetails; +import org.jupnp.model.meta.ModelDetails; import org.jupnp.model.meta.RemoteDevice; -import org.openhab.core.addon.AddonDiscoveryMethod; +import org.jupnp.registry.Registry; +import org.jupnp.registry.RegistryListener; import org.openhab.core.addon.AddonInfo; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This is a {@link UpnpAddonSuggestionFinder} for finding suggested Addons via UPnP. @@ -31,43 +44,120 @@ */ @NonNullByDefault @Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME) -public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder { +public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder implements RegistryListener { public static final String SERVICE_TYPE = "upnp"; public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; + private static final String DEVICE_TYPE = "deviceType"; + private static final String MANUFACTURER = "manufacturer"; + private static final String MANUFACTURER_URI = "manufacturerURI"; + private static final String MODEL_NAME = "modelName"; + private static final String MODEL_NUMBER = "modelNumber"; + private static final String SERIAL_NUMBER = "serialNumber"; + private static final String FRIENDLY_NAME = "friendlyName"; + private static final String UDN = "udn"; + + private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI, + MODEL_NAME, MODEL_NUMBER, SERIAL_NUMBER, FRIENDLY_NAME, UDN); + + private final Logger logger = LoggerFactory.getLogger(UpnpAddonSuggestionFinder.class); + private final Set devices = ConcurrentHashMap.newKeySet(); private final UpnpService upnpService; @Activate public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; + this.upnpService.getRegistry().addListener(this); } - @Override - public void scanTask() { - for (RemoteDevice device : upnpService.getRegistry().getRemoteDevices()) { - for (AddonInfo candidate : addonCandidates) { - for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods()) { - if (Thread.interrupted()) { - // using nested for loops instead of forEach to allow external interruption - return; - } - if (!SERVICE_TYPE.equals(method.getServiceType())) { - continue; - } - Map map = method.getMatchProperties().stream() - .collect(Collectors.toMap(e -> e.getName(), e -> e.getRegex())); - if (propertyMatches(map, "deviceType", device.getType().getType()) - && propertyMatches(map, "manufacturer", - device.getDetails().getManufacturerDetails().getManufacturer()) - && propertyMatches(map, "modelName", device.getDetails().getModelDetails().getModelName()) - && propertyMatches(map, "serialNumber", device.getDetails().getSerialNumber()) - && propertyMatches(map, "udn", device.getIdentity().getUdn().getIdentifierString())) { - addonSuggestionUIDs.add(candidate.getUID()); - } - } - } + public void addDevice(@Nullable RemoteDevice device) { + if (device != null) { + devices.add(device); } - scanDone = true; + } + + @Deactivate + public void close() { + devices.clear(); + super.close(); + } + + @Override + public Set getSuggestedAddons() { + Set result = new HashSet<>(); + addonCandidates.forEach(candidate -> { + candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) + .forEach(method -> { + Map map = method.getMatchProperties().stream().collect( + Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + + Set propNames = new HashSet<>(map.keySet()); + propNames.removeAll(SUPPORTED_PROPERTIES); + if (!propNames.isEmpty()) { + logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", + candidate.getUID(), String.join(",", propNames)); + } else { + devices.stream().forEach(device -> { + DeviceDetails deviceDetails = device.getDetails(); + ManufacturerDetails manufacturerDetails = deviceDetails.getManufacturerDetails(); + ModelDetails modelDetails = deviceDetails.getModelDetails(); + if (propertyMatches(map, DEVICE_TYPE, device.getType().getType()) + && propertyMatches(map, MANUFACTURER, manufacturerDetails.getManufacturer()) + && propertyMatches(map, MANUFACTURER_URI, + manufacturerDetails.getManufacturerURI().toString()) + && propertyMatches(map, MODEL_NAME, modelDetails.getModelName()) + && propertyMatches(map, MODEL_NUMBER, modelDetails.getModelNumber()) + && propertyMatches(map, SERIAL_NUMBER, deviceDetails.getSerialNumber()) + && propertyMatches(map, FRIENDLY_NAME, deviceDetails.getFriendlyName()) + && propertyMatches(map, UDN, + device.getIdentity().getUdn().getIdentifierString())) { + result.add(candidate); + logger.debug("Addon '{}' will be suggested", candidate.getUID()); + } + }); + } + }); + }); + return result; + } + + @Override + public void afterShutdown() { + } + + @Override + public void beforeShutdown(@Nullable Registry registry) { + } + + @Override + public void localDeviceAdded(@Nullable Registry registry, @Nullable LocalDevice localDevice) { + } + + @Override + public void localDeviceRemoved(@Nullable Registry registry, @Nullable LocalDevice localDevice) { + } + + @Override + public void remoteDeviceAdded(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { + addDevice(remoteDevice); + } + + @Override + public void remoteDeviceDiscoveryFailed(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice, + @Nullable Exception exception) { + } + + @Override + public void remoteDeviceDiscoveryStarted(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { + } + + @Override + public void remoteDeviceRemoved(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { + } + + @Override + public void remoteDeviceUpdated(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { + addDevice(remoteDevice); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 7a6c0329776..ba4b078e68d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -54,13 +54,12 @@ import org.jupnp.model.meta.RemoteService; import org.jupnp.model.types.DeviceType; import org.jupnp.model.types.UDN; +import org.jupnp.registry.Registry; import org.mockito.Mockito; -import org.openhab.core.addon.Addon; import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonMatchProperty; -import org.openhab.core.addon.AddonService; import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; import org.openhab.core.config.discovery.addon.finders.UpnpAddonSuggestionFinder; @@ -79,7 +78,6 @@ public class AddonSuggestionFinderServiceTests { private @NonNullByDefault({}) LocaleProvider localeProvider; private @NonNullByDefault({}) MDNSClient mdnsClient; private @NonNullByDefault({}) UpnpService upnpService; - private @NonNullByDefault({}) AddonService addonService; private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider; private @NonNullByDefault({}) AddonSuggestionFinderService addonSuggestionFinderService; @@ -96,7 +94,6 @@ public void cleanUp() { @BeforeAll public void setup() { setupMockLocaleProvider(); - setupMockAddonService(); setupMockAddonInfoProvider(); setupMockMdnsClient(); setupMockUpnpService(); @@ -104,28 +101,22 @@ public void setup() { } private void createAddonSuggestionFinderService() { - addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider); + addonSuggestionFinderService = new AddonSuggestionFinderService(); assertNotNull(addonSuggestionFinderService); - addonSuggestionFinderService.addAddonSuggestionFinder(new UpnpAddonSuggestionFinder(upnpService)); - addonSuggestionFinderService.addAddonSuggestionFinder(new MDNSAddonSuggestionFinder(mdnsClient)); - addonSuggestionFinderService.addAddonService(addonService); - } - - private void setupMockAddonService() { - // create the mock - addonService = mock(AddonService.class); - List addons = new ArrayList<>(); - addons.add(Addon.create("binding-hue").withType("binding").withId("hue").build()); - addons.add(Addon.create("binding-hpprinter").withType("binding").withId("hpprinter").build()); - when(addonService.getAddons(any(Locale.class))).thenReturn(addons); + UpnpAddonSuggestionFinder upnp = new UpnpAddonSuggestionFinder(upnpService); + Registry registry = upnpService.getRegistry(); + registry.getRemoteDevices().forEach(device -> upnp.remoteDeviceAdded(registry, device)); + addonSuggestionFinderService.addAddonSuggestionFinder(upnp); - // check that it works - assertNotNull(addonService); - assertEquals(2, addonService.getAddons(Locale.US).size()); - assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding-hue".equals(a.getUid()))); - assertTrue(addonService.getAddons(Locale.US).stream().anyMatch(a -> "binding-hpprinter".equals(a.getUid()))); - assertFalse(addonService.getAddons(Locale.US).stream().anyMatch(a -> "aardvark".equals(a.getUid()))); + MDNSAddonSuggestionFinder mdns = new MDNSAddonSuggestionFinder(mdnsClient); + for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { + mdns.addService(service); + } + for (ServiceInfo service : mdnsClient.list("_printer._tcp.local.")) { + mdns.addService(service); + } + addonSuggestionFinderService.addAddonSuggestionFinder(mdns); } private void setupMockAddonInfoProvider() { @@ -172,9 +163,9 @@ private void setupMockMdnsClient() { // create the mock mdnsClient = mock(MDNSClient.class, Mockito.RETURNS_DEEP_STUBS); when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {}); - ServiceInfo hueService = ServiceInfo.create("mdnsTest", "hue", 0, 0, 0, false, "hue service"); + ServiceInfo hueService = ServiceInfo.create("hue", "hue", 0, 0, 0, false, "hue service"); when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService }); - ServiceInfo hpService = ServiceInfo.create("mdnsTest", "hpprinter", 0, 0, 0, false, "hp printer service"); + ServiceInfo hpService = ServiceInfo.create("printer", "hpprinter", 0, 0, 0, false, "hp printer service"); hpService.setText(Map.of("ty", "hp printer", "rp", "anything")); when(mdnsClient.list(eq("_printer._tcp.local."))).thenReturn(new ServiceInfo[] { hpService }); @@ -215,13 +206,13 @@ private void setupMockUpnpService() { ModelDetails modDetails = new ModelDetails("Philips hue bridge", "modelDescription", "modelNumber", "modelURI"); DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber", "000123456789"); - List<@Nullable RemoteDevice> remoteDevice = new ArrayList<>(); + List<@Nullable RemoteDevice> remoteDevices = new ArrayList<>(); try { - remoteDevice.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null)); + remoteDevices.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null)); } catch (ValidationException e1) { fail("ValidationException"); } - when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevice); + when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevices); // check that it works assertNotNull(upnpService); @@ -233,18 +224,12 @@ private void setupMockUpnpService() { } @Test - public void testGetAddons() { + public void testGetSuggestedAddons() { addonSuggestionFinderService.addAddonInfoProvider(addonInfoProvider); - // give the scan tasks some time to complete - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - assertTrue(addonSuggestionFinderService.scanDone()); - List addons = addonSuggestionFinderService.getSuggestedAddons(Locale.US); + Set addons = addonSuggestionFinderService.getSuggestedAddons(localeProvider.getLocale()); assertEquals(2, addons.size()); - assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUid()))); - assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUid()))); - assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUid()))); + assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); } } diff --git a/bundles/org.openhab.core.thing/.classpath b/bundles/org.openhab.core.thing/.classpath deleted file mode 100644 index 50b5b3d2e8a..00000000000 --- a/bundles/org.openhab.core.thing/.classpath +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 17154630467579f21bba790df29d102aa9bbdb23 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 26 Oct 2023 13:10:37 +0100 Subject: [PATCH 31/98] [suggested addon finder] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/addon/AddonInfo.java | 17 +++++---- .../core/addon/AddonMatchProperty.java | 10 ++++-- bundles/org.openhab.core.thing/.classpath | 35 +++++++++++++++++++ .../core/addon/xml/test/AddonInfoTest.java | 2 ++ 4 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 bundles/org.openhab.core.thing/.classpath diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index 760d6e996b5..d1be2cde404 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -47,10 +47,10 @@ public class AddonInfo implements Identifiable { private @Nullable String sourceBundle; private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, String name, String description, @Nullable String connection, - List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, - @Nullable String sourceBundle, @Nullable List discoveryMethods) - throws IllegalArgumentException { + private AddonInfo(String id, String type, @Nullable String uid, String name, String description, + @Nullable String connection, List countries, @Nullable String configDescriptionURI, + @Nullable String serviceId, @Nullable String sourceBundle, + @Nullable List discoveryMethods) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -193,6 +193,11 @@ private Builder(AddonInfo addonInfo) { this.discoveryMethods = addonInfo.discoveryMethods; } + public Builder withUID(String uid) { + this.uid = uid; + return this; + } + public Builder withName(String name) { this.name = name; return this; @@ -245,8 +250,8 @@ public Builder withDiscoveryMethods(@Nullable List discove * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, - sourceBundle, discoveryMethods); + return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, + serviceId, sourceBundle, discoveryMethods); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java index df16ef89dbd..60ef75cdd27 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -24,10 +24,12 @@ @NonNullByDefault public class AddonMatchProperty { private @NonNullByDefault({}) String name; - private @NonNullByDefault({}) Pattern pattern; + private @NonNullByDefault({}) String regex; + private transient @NonNullByDefault({}) Pattern pattern; public AddonMatchProperty(String name, String regex) { this.name = name; + this.regex = regex; this.pattern = Pattern.compile(regex); } @@ -36,6 +38,10 @@ public String getName() { } public Pattern getPattern() { - return this.pattern; + return pattern; + } + + public String getRegex() { + return regex; } } diff --git a/bundles/org.openhab.core.thing/.classpath b/bundles/org.openhab.core.thing/.classpath new file mode 100644 index 00000000000..5a77c00dbc1 --- /dev/null +++ b/bundles/org.openhab.core.thing/.classpath @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java index 62a49060580..3e109b7a5a9 100644 --- a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java +++ b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java @@ -17,6 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.List; @@ -95,6 +96,7 @@ public void assertThatAddonInfoIsReadProperly() throws Exception { assertNotNull(property); assertEquals("modelName", property.getName()); assertEquals("Philips hue bridge", property.getRegex()); + assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); }); } From 481c764d9bad2fdda1923aaa70ea8b230d755ea4 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 26 Oct 2023 18:59:39 +0100 Subject: [PATCH 32/98] [suggested addon finder] merge addonInfo's Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/AddonInfoRegistry.java | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 338a3cbfad6..7b07f2ed4b4 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -21,6 +22,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BinaryOperator; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -66,18 +68,59 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { * Returns the add-on information for the specified add-on UID and locale (language), * or {@code null} if no add-on information could be found. *

- * If more than one provider provides information for the specified add-on UID and locale, - * it returns a new {@link AddonInfo} containing merged information from all such providers. + * If more than one provider provides information for the specified add-on ID and locale, + * it returns merged information from all such providers. * - * @param uid the UID to be looked for + * @param targetId the ID to be looked for * @param locale the locale to be used for the add-on information (could be null) * @return a localized add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) { - return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)).filter(Objects::nonNull) - .collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(), - Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get))) - .get(uid); + public @Nullable AddonInfo getAddonInfo(String targetId, @Nullable Locale locale) { + // note: using funky code to prevent a maven compiler error + Stream addonInfos = addonInfoProviders.stream() + .map(p -> Optional.ofNullable(p.getAddonInfo(targetId, locale))).filter(o -> o.isPresent()) + .map(o -> o.get()); + + // one or zero entries + if (addonInfos.count() <= 1) { + return addonInfos.findAny().orElse(null); + } + + // multiple entries + String id = null; + String type = null; + String uid = null; + String name = null; + String description = null; + String connection = null; + String configDescriptionURI = null; + String serviceId = null; + String sourceBundle = null; + List discoveryMethods = List.of(); + Set countries = new HashSet<>(); + + for (AddonInfo addonInfo : addonInfos.toList()) { + // unique fields: take first non null value + id = id != null ? id : addonInfo.getId(); + type = type != null ? type : addonInfo.getType(); + uid = uid != null ? uid : addonInfo.getUID(); + name = name != null ? name : addonInfo.getName(); + description = description != null ? description : addonInfo.getDescription(); + connection = connection != null ? connection : addonInfo.getConnection(); + configDescriptionURI = configDescriptionURI != null ? configDescriptionURI + : addonInfo.getConfigDescriptionURI(); + serviceId = serviceId != null ? serviceId : addonInfo.getServiceId(); + sourceBundle = sourceBundle != null ? sourceBundle : addonInfo.getSourceBundle(); + discoveryMethods = !discoveryMethods.isEmpty() ? discoveryMethods : addonInfo.getDiscoveryMethods(); + // list field: uniquely combine via a set + countries.addAll(addonInfo.getCountries()); + } + + return AddonInfo.builder(Objects.requireNonNull(id), Objects.requireNonNull(type)) + .withUID(Objects.requireNonNull(uid)).withName(Objects.requireNonNull(name)) + .withDescription(Objects.requireNonNull(description)).withConnection(connection) + .withCountries(countries.stream().toList()).withConfigDescriptionURI(configDescriptionURI) + .withServiceId(serviceId).withSourceBundle(sourceBundle).withDiscoveryMethods(discoveryMethods).build(); } /** From 12e2c91dc43ae18e3802b62ed1dfc44bed8fee88 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 26 Oct 2023 22:39:16 +0100 Subject: [PATCH 33/98] [suggested addon finder] adopt reviewer suggestion Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/AddonInfoRegistry.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 7b07f2ed4b4..3cc5188cc7a 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -22,7 +22,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BinaryOperator; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -77,13 +76,18 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { */ public @Nullable AddonInfo getAddonInfo(String targetId, @Nullable Locale locale) { // note: using funky code to prevent a maven compiler error - Stream addonInfos = addonInfoProviders.stream() + List addonInfos = addonInfoProviders.stream() .map(p -> Optional.ofNullable(p.getAddonInfo(targetId, locale))).filter(o -> o.isPresent()) - .map(o -> o.get()); + .map(o -> o.get()).toList(); // one or zero entries - if (addonInfos.count() <= 1) { - return addonInfos.findAny().orElse(null); + switch (addonInfos.size()) { + case 0: + return null; + case 1: + return addonInfos.get(0); + default: + // fall through } // multiple entries @@ -99,7 +103,7 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { List discoveryMethods = List.of(); Set countries = new HashSet<>(); - for (AddonInfo addonInfo : addonInfos.toList()) { + for (AddonInfo addonInfo : addonInfos) { // unique fields: take first non null value id = id != null ? id : addonInfo.getId(); type = type != null ? type : addonInfo.getType(); From 9d86372b5fa6d8d423684cda1a14e70ff2b628d3 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 27 Oct 2023 10:52:58 +0100 Subject: [PATCH 34/98] [suggested addon finder] fix javadoc; fix enrichment Signed-off-by: Andrew Fiddian-Green --- .../addon/eclipse/internal/EclipseAddonService.java | 1 - .../core/karaf/internal/KarafAddonService.java | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java index 6e5f063850a..e975396ef7d 100644 --- a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java +++ b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java @@ -142,7 +142,6 @@ private Addon getAddon(Bundle bundle, @Nullable Locale locale) { AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale); if (addonInfo != null) { - // only enrich if this add-on is installed, otherwise wrong data might be added addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription()) .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withLink(getDefaultDocumentationLink(type, name)) diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java index 8e422715568..df8e1f33540 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java @@ -13,7 +13,13 @@ package org.openhab.core.karaf.internal; import static java.util.Map.entry; -import static org.openhab.core.addon.AddonType.*; +import static org.openhab.core.addon.AddonType.AUTOMATION; +import static org.openhab.core.addon.AddonType.BINDING; +import static org.openhab.core.addon.AddonType.MISC; +import static org.openhab.core.addon.AddonType.PERSISTENCE; +import static org.openhab.core.addon.AddonType.TRANSFORMATION; +import static org.openhab.core.addon.AddonType.UI; +import static org.openhab.core.addon.AddonType.VOICE; import java.net.URI; import java.util.Arrays; @@ -133,8 +139,7 @@ private Addon getAddon(Feature feature, @Nullable Locale locale) { AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale); - if (isInstalled && addonInfo != null) { - // only enrich if this add-on is installed, otherwise wrong data might be added + if (addonInfo != null) { addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription()) .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withLink(getDefaultDocumentationLink(type, name)) From 5b331c0b53986921dff7679638c93d2f7ac0d9eb Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 27 Oct 2023 16:32:06 +0100 Subject: [PATCH 35/98] [suggested addon finder] add equals(), hash() Signed-off-by: Andrew Fiddian-Green --- .../core/addon/AddonDiscoveryMethod.java | 22 +++++++++++++++++++ .../core/addon/AddonMatchProperty.java | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java index 2e923a2f965..88eb8669819 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -13,6 +13,7 @@ package org.openhab.core.addon; import java.util.List; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -56,4 +57,25 @@ public AddonDiscoveryMethod setMatchProperties(@Nullable List Date: Fri, 27 Oct 2023 16:32:29 +0100 Subject: [PATCH 36/98] [suggested addon finder] remove uid Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/addon/AddonInfo.java | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index d1be2cde404..a6d29ec3c61 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -37,7 +37,6 @@ public class AddonInfo implements Identifiable { private final String id; private final String type; - private final String uid; private final String name; private final String description; private final @Nullable String connection; @@ -47,10 +46,10 @@ public class AddonInfo implements Identifiable { private @Nullable String sourceBundle; private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, @Nullable String uid, String name, String description, - @Nullable String connection, List countries, @Nullable String configDescriptionURI, - @Nullable String serviceId, @Nullable String sourceBundle, - @Nullable List discoveryMethods) throws IllegalArgumentException { + private AddonInfo(String id, String type, String name, String description, @Nullable String connection, + List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, + @Nullable String sourceBundle, @Nullable List discoveryMethods) + throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -67,7 +66,6 @@ private AddonInfo(String id, String type, @Nullable String uid, String name, Str } this.id = id; this.type = type; - this.uid = uid != null ? uid : type + Addon.ADDON_SEPARATOR + id; this.name = name; this.description = description; @@ -87,7 +85,7 @@ private AddonInfo(String id, String type, @Nullable String uid, String name, Str */ @Override public String getUID() { - return uid; + return type + Addon.ADDON_SEPARATOR + id; } /** @@ -164,7 +162,6 @@ public static class Builder { private final String id; private final String type; - private @Nullable String uid; private String name = ""; private String description = ""; private @Nullable String connection; @@ -182,7 +179,6 @@ private Builder(String id, String type) { private Builder(AddonInfo addonInfo) { this.id = addonInfo.id; this.type = addonInfo.type; - this.uid = addonInfo.uid; this.name = addonInfo.name; this.description = addonInfo.description; this.connection = addonInfo.connection; @@ -193,11 +189,6 @@ private Builder(AddonInfo addonInfo) { this.discoveryMethods = addonInfo.discoveryMethods; } - public Builder withUID(String uid) { - this.uid = uid; - return this; - } - public Builder withName(String name) { this.name = name; return this; @@ -250,8 +241,8 @@ public Builder withDiscoveryMethods(@Nullable List discove * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, - serviceId, sourceBundle, discoveryMethods); + return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, + sourceBundle, discoveryMethods); } } } From ff8f48417a759e173a3e661157efa1a19cb1e9b9 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 27 Oct 2023 16:33:06 +0100 Subject: [PATCH 37/98] [suggested addon finder] fix getAddonInfo(), mergeAddonInfos() Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/AddonInfoRegistry.java | 75 +++---------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 3cc5188cc7a..4d242b8d71b 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -14,9 +14,7 @@ import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -67,64 +65,18 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { * Returns the add-on information for the specified add-on UID and locale (language), * or {@code null} if no add-on information could be found. *

- * If more than one provider provides information for the specified add-on ID and locale, - * it returns merged information from all such providers. + * If more than one provider provides information for the specified add-on UID and locale, + * it returns a new {@link AddonInfo} containing merged information from all such providers. * - * @param targetId the ID to be looked for + * @param uid the UID to be looked for * @param locale the locale to be used for the add-on information (could be null) * @return a localized add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String targetId, @Nullable Locale locale) { - // note: using funky code to prevent a maven compiler error - List addonInfos = addonInfoProviders.stream() - .map(p -> Optional.ofNullable(p.getAddonInfo(targetId, locale))).filter(o -> o.isPresent()) - .map(o -> o.get()).toList(); - - // one or zero entries - switch (addonInfos.size()) { - case 0: - return null; - case 1: - return addonInfos.get(0); - default: - // fall through - } - - // multiple entries - String id = null; - String type = null; - String uid = null; - String name = null; - String description = null; - String connection = null; - String configDescriptionURI = null; - String serviceId = null; - String sourceBundle = null; - List discoveryMethods = List.of(); - Set countries = new HashSet<>(); - - for (AddonInfo addonInfo : addonInfos) { - // unique fields: take first non null value - id = id != null ? id : addonInfo.getId(); - type = type != null ? type : addonInfo.getType(); - uid = uid != null ? uid : addonInfo.getUID(); - name = name != null ? name : addonInfo.getName(); - description = description != null ? description : addonInfo.getDescription(); - connection = connection != null ? connection : addonInfo.getConnection(); - configDescriptionURI = configDescriptionURI != null ? configDescriptionURI - : addonInfo.getConfigDescriptionURI(); - serviceId = serviceId != null ? serviceId : addonInfo.getServiceId(); - sourceBundle = sourceBundle != null ? sourceBundle : addonInfo.getSourceBundle(); - discoveryMethods = !discoveryMethods.isEmpty() ? discoveryMethods : addonInfo.getDiscoveryMethods(); - // list field: uniquely combine via a set - countries.addAll(addonInfo.getCountries()); - } - - return AddonInfo.builder(Objects.requireNonNull(id), Objects.requireNonNull(type)) - .withUID(Objects.requireNonNull(uid)).withName(Objects.requireNonNull(name)) - .withDescription(Objects.requireNonNull(description)).withConnection(connection) - .withCountries(countries.stream().toList()).withConfigDescriptionURI(configDescriptionURI) - .withServiceId(serviceId).withSourceBundle(sourceBundle).withDiscoveryMethods(discoveryMethods).build(); + public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) { + return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)) + .collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(), + Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get))) + .get(uid); } /** @@ -155,21 +107,12 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { if (!countries.isEmpty()) { builder.withCountries(countries.stream().toList()); } - String aConfigDescriptionURI = a.getConfigDescriptionURI(); - if (aConfigDescriptionURI == null || aConfigDescriptionURI.isEmpty() && b.getConfigDescriptionURI() != null) { + if (a.getConfigDescriptionURI() == null && b.getConfigDescriptionURI() != null) { builder.withConfigDescriptionURI(b.getConfigDescriptionURI()); } if (a.getSourceBundle() == null && b.getSourceBundle() != null) { builder.withSourceBundle(b.getSourceBundle()); } - String defaultServiceId = a.getType() + "." + a.getId(); - if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) { - builder.withServiceId(b.getServiceId()); - } - String defaultUID = a.getType() + Addon.ADDON_SEPARATOR + a.getId(); - if (defaultUID.equals(a.getUID()) && !defaultUID.equals(b.getUID())) { - builder.withUID(b.getUID()); - } Set discoveryMethods = new HashSet<>(a.getDiscoveryMethods()); discoveryMethods.addAll(b.getDiscoveryMethods()); if (!discoveryMethods.isEmpty()) { From 1011efa36e31137fcfdf1806060aa1dbf3a6e361 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 27 Oct 2023 19:32:31 +0100 Subject: [PATCH 38/98] [suggested addon finder] add .filter(Objects::nonNull) Signed-off-by: Andrew Fiddian-Green --- .../main/java/org/openhab/core/addon/AddonInfoRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 4d242b8d71b..b0c68d27db9 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -15,6 +15,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Locale; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -73,7 +74,7 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { * @return a localized add-on information object (could be null) */ public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) { - return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)) + return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)).filter(Objects::nonNull) .collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(), Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get))) .get(uid); From 614c3a26ea5eee2f85a6c7796a30a316a2d33723 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 27 Oct 2023 23:17:17 +0100 Subject: [PATCH 39/98] [suggested addon finder] add JUnit test for merging Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/AddonInfoRegistry.java | 5 +- .../core/addon/test/AddonInfoMergeTest.java | 199 ++++++++++++++++++ 2 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index b0c68d27db9..10e7c2d8619 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -43,7 +43,7 @@ public class AddonInfoRegistry { private final Collection addonInfoProviders = new CopyOnWriteArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); } @@ -108,7 +108,8 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { if (!countries.isEmpty()) { builder.withCountries(countries.stream().toList()); } - if (a.getConfigDescriptionURI() == null && b.getConfigDescriptionURI() != null) { + String aConfigDescriptionURI = a.getConfigDescriptionURI(); + if (aConfigDescriptionURI == null || aConfigDescriptionURI.isEmpty() && b.getConfigDescriptionURI() != null) { builder.withConfigDescriptionURI(b.getConfigDescriptionURI()); } if (a.getSourceBundle() == null && b.getSourceBundle() != null) { diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java new file mode 100644 index 00000000000..3cde11b0202 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java @@ -0,0 +1,199 @@ +/** + * 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.addon.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonInfoRegistry; +import org.openhab.core.addon.AddonMatchProperty; + +/** + * JUnit test for the {@link AddonInfoRegistry} merge function. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +class AddonInfoMergeTest { + + private @Nullable AddonInfoProvider addonInfoProvider0; + private @Nullable AddonInfoProvider addonInfoProvider1; + private @Nullable AddonInfoProvider addonInfoProvider2; + + @BeforeAll + void beforeAll() { + addonInfoProvider0 = createAddonInfoProvider0(); + addonInfoProvider1 = createAddonInfoProvider1(); + addonInfoProvider2 = createAddonInfoProvider2(); + } + + private AddonInfoProvider createAddonInfoProvider0() { + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero") + .withDescription("description-zero").build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + private AddonInfoProvider createAddonInfoProvider1() { + AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns") + .setMdnsServiceType("_hue._tcp.local."); + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one") + .withDescription("description-one").withCountries("GB,NL").withConnection("local") + .withDiscoveryMethods(List.of(discoveryMethod)).build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + private AddonInfoProvider createAddonInfoProvider2() { + AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("upnp") + .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") + .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") + .withConfigDescriptionURI("http://www.disney.com").withDiscoveryMethods(List.of(discoveryMethod)) + .build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + /** + * Test fetching a single addon-info from the registry with no merging. + */ + @Test + void testGetOneAddonInfo() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertNotEquals("local", addonInfo.getConnection()); + assertEquals(0, addonInfo.getCountries().size()); + assertNotEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertEquals(0, addonInfo.getDiscoveryMethods().size()); + } + + /** + * Test fetching two addon-info's from the registry with merging. + */ + @Test + void testMergeAddonInfos2() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + assertNotNull(addonInfoProvider1); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertNotEquals("source-bundle", addonInfo.getSourceBundle()); + assertEquals("local", addonInfo.getConnection()); + assertEquals(2, addonInfo.getCountries().size()); + assertNotEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertEquals(1, addonInfo.getDiscoveryMethods().size()); + } + + /** + * Test fetching three addon-info's from the registry with full merging. + */ + @Test + void testMergeAddonInfos3() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + assertNotNull(addonInfoProvider1); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); + assertNotNull(addonInfoProvider2); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider2)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertEquals("source-bundle", addonInfo.getSourceBundle()); + assertEquals("local", addonInfo.getConnection()); + assertEquals(4, addonInfo.getCountries().size()); + assertEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertEquals(2, addonInfo.getDiscoveryMethods().size()); + } +} From 3108edc816cbaaf447cc5c1b996a17eea268ceb8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 28 Oct 2023 13:56:46 +0100 Subject: [PATCH 40/98] [suggested addon finder] refactor junits tests Signed-off-by: Andrew Fiddian-Green --- .../addon/test/AddonInfoListReaderTest.java | 121 ++++++++++++++++++ ...t.java => AddonInfoRegistryMergeTest.java} | 10 +- 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java rename bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/{AddonInfoMergeTest.java => AddonInfoRegistryMergeTest.java} (95%) diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java new file mode 100644 index 00000000000..bc20aaa6620 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java @@ -0,0 +1,121 @@ +/** + * 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.addon.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.addon.AddonInfoListReader; +import org.openhab.core.addon.AddonMatchProperty; + +/** + * JUnit tests for {@link AddonInfoListReader}. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +class AddonInfoListReaderTest { + + // @formatter:off + private final String testXml = + "" + + " " + + " automation" + + " Groovy Scripting" + + " This adds a Groovy script engine." + + " none" + + " " + + " " + + " mdns" + + " _printer._tcp.local." + + " " + + " " + + " rp" + + " .*" + + " " + + " " + + " ty" + + " hp (.*)" + + " " + + " " + + " " + + " " + + " upnp" + + " " + + " " + + " modelName" + + " Philips hue bridge" + + " " + + " " + + " " + + " " + + " " + + ""; + // @formatter:on + + @Test + void testAddonInfoListReader() { + AddonInfoList addons = null; + try { + AddonInfoListReader reader = new AddonInfoListReader(); + addons = reader.readFromXML(testXml); + } catch (Exception e) { + fail(e); + } + assertNotNull(addons); + List addonsInfos = addons.getAddons(); + assertEquals(1, addonsInfos.size()); + AddonInfo addon = addonsInfos.get(0); + assertNotNull(addon); + List discoveryMethods = addon.getDiscoveryMethods(); + assertNotNull(discoveryMethods); + assertEquals(2, discoveryMethods.size()); + + AddonDiscoveryMethod method = discoveryMethods.get(0); + assertNotNull(method); + assertEquals("mdns", method.getServiceType()); + assertEquals("_printer._tcp.local.", method.getMdnsServiceType()); + List matchProperties = method.getMatchProperties(); + assertNotNull(matchProperties); + assertEquals(2, matchProperties.size()); + AddonMatchProperty property = matchProperties.get(0); + assertNotNull(property); + assertEquals("rp", property.getName()); + assertEquals(".*", property.getRegex()); + assertTrue(property.getPattern().matcher("the cat sat on the mat").matches()); + + method = discoveryMethods.get(1); + assertNotNull(method); + assertEquals("upnp", method.getServiceType()); + assertEquals("", method.getMdnsServiceType()); + matchProperties = method.getMatchProperties(); + assertNotNull(matchProperties); + assertEquals(1, matchProperties.size()); + property = matchProperties.get(0); + assertNotNull(property); + assertEquals("modelName", property.getName()); + assertEquals("Philips hue bridge", property.getRegex()); + assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); + } +} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java similarity index 95% rename from bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java rename to bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java index 3cde11b0202..ce5caf07f0b 100644 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoMergeTest.java +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java @@ -46,7 +46,7 @@ */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -class AddonInfoMergeTest { +class AddonInfoRegistryMergeTest { private @Nullable AddonInfoProvider addonInfoProvider0; private @Nullable AddonInfoProvider addonInfoProvider1; @@ -89,7 +89,7 @@ private AddonInfoProvider createAddonInfoProvider2() { .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") - .withConfigDescriptionURI("http://www.disney.com").withDiscoveryMethods(List.of(discoveryMethod)) + .withConfigDescriptionURI("http://www.openhab.org").withDiscoveryMethods(List.of(discoveryMethod)) .build(); AddonInfoProvider provider = mock(AddonInfoProvider.class); when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); @@ -125,7 +125,7 @@ void testGetOneAddonInfo() { assertTrue(addonInfo.getDescription().startsWith("description-")); assertNotEquals("local", addonInfo.getConnection()); assertEquals(0, addonInfo.getCountries().size()); - assertNotEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); assertEquals(0, addonInfo.getDiscoveryMethods().size()); } @@ -158,7 +158,7 @@ void testMergeAddonInfos2() { assertNotEquals("source-bundle", addonInfo.getSourceBundle()); assertEquals("local", addonInfo.getConnection()); assertEquals(2, addonInfo.getCountries().size()); - assertNotEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); assertEquals(1, addonInfo.getDiscoveryMethods().size()); } @@ -193,7 +193,7 @@ void testMergeAddonInfos3() { assertEquals("source-bundle", addonInfo.getSourceBundle()); assertEquals("local", addonInfo.getConnection()); assertEquals(4, addonInfo.getCountries().size()); - assertEquals("http://www.disney.com", addonInfo.getConfigDescriptionURI()); + assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); assertEquals(2, addonInfo.getDiscoveryMethods().size()); } } From 5c71da114aa18df51b282b8b20dce1ccda0e4215 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 28 Oct 2023 15:10:48 +0100 Subject: [PATCH 41/98] [suggested addon finder] serviceId merge and tests Signed-off-by: Andrew Fiddian-Green --- .../java/org/openhab/core/addon/AddonInfoRegistry.java | 4 ++++ .../core/addon/test/AddonInfoRegistryMergeTest.java | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 10e7c2d8619..08f82b05f34 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -115,6 +115,10 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { if (a.getSourceBundle() == null && b.getSourceBundle() != null) { builder.withSourceBundle(b.getSourceBundle()); } + String defaultServiceId = a.getType() + "." + a.getId(); + if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) { + builder.withServiceId(b.getServiceId()); + } Set discoveryMethods = new HashSet<>(a.getDiscoveryMethods()); discoveryMethods.addAll(b.getDiscoveryMethods()); if (!discoveryMethods.isEmpty()) { diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java index ce5caf07f0b..e7cb73cd6dd 100644 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java @@ -89,8 +89,8 @@ private AddonInfoProvider createAddonInfoProvider2() { .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") - .withConfigDescriptionURI("http://www.openhab.org").withDiscoveryMethods(List.of(discoveryMethod)) - .build(); + .withServiceId("service-id").withConfigDescriptionURI("http://www.openhab.org") + .withDiscoveryMethods(List.of(discoveryMethod)).build(); AddonInfoProvider provider = mock(AddonInfoProvider.class); when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); @@ -123,9 +123,11 @@ void testGetOneAddonInfo() { assertEquals("binding-hue", addonInfo.getUID()); assertTrue(addonInfo.getName().startsWith("name-")); assertTrue(addonInfo.getDescription().startsWith("description-")); + assertNull(addonInfo.getSourceBundle()); assertNotEquals("local", addonInfo.getConnection()); assertEquals(0, addonInfo.getCountries().size()); assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("binding.hue", addonInfo.getServiceId()); assertEquals(0, addonInfo.getDiscoveryMethods().size()); } @@ -155,10 +157,11 @@ void testMergeAddonInfos2() { assertEquals("binding-hue", addonInfo.getUID()); assertTrue(addonInfo.getName().startsWith("name-")); assertTrue(addonInfo.getDescription().startsWith("description-")); - assertNotEquals("source-bundle", addonInfo.getSourceBundle()); + assertNull(addonInfo.getSourceBundle()); assertEquals("local", addonInfo.getConnection()); assertEquals(2, addonInfo.getCountries().size()); assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("binding.hue", addonInfo.getServiceId()); assertEquals(1, addonInfo.getDiscoveryMethods().size()); } @@ -194,6 +197,7 @@ void testMergeAddonInfos3() { assertEquals("local", addonInfo.getConnection()); assertEquals(4, addonInfo.getCountries().size()); assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("service-id", addonInfo.getServiceId()); assertEquals(2, addonInfo.getDiscoveryMethods().size()); } } From 729806bdf50efd57caacd068c8c81826a8c8ffe2 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 29 Oct 2023 13:06:16 +0000 Subject: [PATCH 42/98] [suggested addon finder] compile pattern on first use Signed-off-by: Andrew Fiddian-Green --- .../java/org/openhab/core/addon/AddonMatchProperty.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java index 8f25f5d898b..ac5ccebe9b3 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -32,7 +32,7 @@ public class AddonMatchProperty { public AddonMatchProperty(String name, String regex) { this.name = name; this.regex = regex; - this.pattern = Pattern.compile(regex); + this.pattern = null; } public String getName() { @@ -40,7 +40,11 @@ public String getName() { } public Pattern getPattern() { - return pattern; + Pattern pattern = this.pattern; + if (pattern == null) { + this.pattern = Pattern.compile(regex); + } + return this.pattern; } public String getRegex() { From 535c80ea30ff60057c247e095fb3b2b07fbc40b6 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 30 Oct 2023 10:27:11 +0000 Subject: [PATCH 43/98] [suggested addon finder] fix memory leak; add trace logging Signed-off-by: Andrew Fiddian-Green --- .../finders/MDNSAddonSuggestionFinder.java | 19 +++++++++++++------ .../finders/UpnpAddonSuggestionFinder.java | 18 +++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 9bf8c21bc76..21991693cb0 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -13,6 +13,7 @@ package org.openhab.core.config.discovery.addon.finders; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,11 +49,10 @@ public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme public static final String SERVICE_TYPE = "mdns"; public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; - private static final String APPLICATION = "application"; private static final String NAME = "name"; private final Logger logger = LoggerFactory.getLogger(MDNSAddonSuggestionFinder.class); - private final Set services = ConcurrentHashMap.newKeySet(); + private final Map services = new ConcurrentHashMap<>(); private final MDNSClient mdnsClient; @Activate @@ -62,7 +62,16 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { public void addService(@Nullable ServiceInfo service) { if (service != null) { - services.add(service); + String qualifiedName = service.getQualifiedName(); + services.put(qualifiedName, service); + if (logger.isTraceEnabled()) { + Map properties = new HashMap<>(); + while (service.getPropertyNames().hasMoreElements()) { + String name = service.getPropertyNames().nextElement(); + properties.put(name, service.getPropertyString(name)); + } + logger.trace("mDNS service name:{}, properties:{}", qualifiedName, properties.toString()); + } } } @@ -80,10 +89,8 @@ public Set getSuggestedAddons() { .forEach(method -> { Map map = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); - - services.stream().forEach(service -> { + services.values().stream().forEach(service -> { if (method.getMdnsServiceType().equals(service.getType()) - && propertyMatches(map, APPLICATION, service.getApplication()) && propertyMatches(map, NAME, service.getName()) && Collections.list(service.getPropertyNames()).stream().allMatch( name -> propertyMatches(map, name, service.getPropertyString(name)))) { diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 6b64ccef7c4..86e2e7d6712 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -62,7 +62,7 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme MODEL_NAME, MODEL_NUMBER, SERIAL_NUMBER, FRIENDLY_NAME, UDN); private final Logger logger = LoggerFactory.getLogger(UpnpAddonSuggestionFinder.class); - private final Set devices = ConcurrentHashMap.newKeySet(); + private final Map devices = new ConcurrentHashMap<>(); private final UpnpService upnpService; @Activate @@ -73,7 +73,19 @@ public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { public void addDevice(@Nullable RemoteDevice device) { if (device != null) { - devices.add(device); + String udnString = device.getIdentity().getUdn().getIdentifierString(); + devices.put(udnString, device); + if (logger.isTraceEnabled()) { + DeviceDetails deviceDetails = device.getDetails(); + ManufacturerDetails manufacturerDetails = deviceDetails.getManufacturerDetails(); + ModelDetails modelDetails = deviceDetails.getModelDetails(); + logger.trace( + "UPnP device type:{}, manufacturer:{}, manufacturerURI:{}, modelName:{}, modelNumber:{}, serialNumber:{}, friendlyName:{}, UDN:{}", + device.getType(), manufacturerDetails.getManufacturer(), + manufacturerDetails.getManufacturerURI().toString(), modelDetails.getModelName(), + modelDetails.getModelNumber(), deviceDetails.getSerialNumber(), deviceDetails.getFriendlyName(), + udnString); + } } } @@ -98,7 +110,7 @@ public Set getSuggestedAddons() { logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", candidate.getUID(), String.join(",", propNames)); } else { - devices.stream().forEach(device -> { + devices.values().stream().forEach(device -> { DeviceDetails deviceDetails = device.getDetails(); ManufacturerDetails manufacturerDetails = deviceDetails.getManufacturerDetails(); ModelDetails modelDetails = deviceDetails.getModelDetails(); From 987f3db9d857eed33191ccd8261f784803d7efa0 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 30 Oct 2023 23:21:31 +0000 Subject: [PATCH 44/98] [suggested addon finder] improve logging, null checks, matching Signed-off-by: Andrew Fiddian-Green --- .../finders/BaseAddonSuggestionFinder.java | 9 +- .../finders/MDNSAddonSuggestionFinder.java | 15 +- .../finders/UpnpAddonSuggestionFinder.java | 129 ++++++++++++++---- 3 files changed, 109 insertions(+), 44 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index 121fdda4104..6f777ad9b0c 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.AddonInfo; /** @@ -39,13 +40,13 @@ public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder * @param propertyPatternMap map of property names and regex patterns for value matching * @param propertyName * @param propertyValue - * @return true a) if the property name exists and the property value matches - * the regular expression, or b) the property name does not exist. + * @return true a) if the property name exists and the property value is not null and matches the regular + * expression, or b) the property name does not exist. */ protected static boolean propertyMatches(Map propertyPatternMap, String propertyName, - String propertyValue) { + @Nullable String propertyValue) { Pattern pattern = propertyPatternMap.get(propertyName); - return pattern == null ? true : pattern.matcher(propertyValue).matches(); + return pattern == null ? true : propertyValue != null ? pattern.matcher(propertyValue).matches() : false; } protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 21991693cb0..8c473679588 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -13,7 +13,6 @@ package org.openhab.core.config.discovery.addon.finders; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -63,14 +62,10 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { public void addService(@Nullable ServiceInfo service) { if (service != null) { String qualifiedName = service.getQualifiedName(); - services.put(qualifiedName, service); - if (logger.isTraceEnabled()) { - Map properties = new HashMap<>(); - while (service.getPropertyNames().hasMoreElements()) { - String name = service.getPropertyNames().nextElement(); - properties.put(name, service.getPropertyString(name)); - } - logger.trace("mDNS service name:{}, properties:{}", qualifiedName, properties.toString()); + if (services.put(qualifiedName, service) == null && logger.isTraceEnabled()) { + logger.trace("mDNS service name={}, properties={}", qualifiedName, + Collections.list(service.getPropertyNames()).stream() + .map(n -> n + "=" + service.getPropertyString(n)).toList()); } } } @@ -95,7 +90,7 @@ && propertyMatches(map, NAME, service.getName()) && Collections.list(service.getPropertyNames()).stream().allMatch( name -> propertyMatches(map, name, service.getPropertyString(name)))) { result.add(candidate); - logger.debug("Addon '{}' will be suggested", candidate.getUID()); + logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); } }); }); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 86e2e7d6712..d3f652a5aad 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -12,7 +12,10 @@ */ package org.openhab.core.config.discovery.addon.finders; +import java.net.URI; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -27,6 +30,7 @@ import org.jupnp.model.meta.ManufacturerDetails; import org.jupnp.model.meta.ModelDetails; import org.jupnp.model.meta.RemoteDevice; +import org.jupnp.model.types.DeviceType; import org.jupnp.registry.Registry; import org.jupnp.registry.RegistryListener; import org.openhab.core.addon.AddonInfo; @@ -51,20 +55,28 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private static final String DEVICE_TYPE = "deviceType"; private static final String MANUFACTURER = "manufacturer"; - private static final String MANUFACTURER_URI = "manufacturerURI"; + private static final String MANUFACTURER_URL = "manufacturerURL"; private static final String MODEL_NAME = "modelName"; private static final String MODEL_NUMBER = "modelNumber"; + private static final String MODEL_DESCRIPTION = "modelDescription"; + private static final String MODEL_URL = "modelURL"; private static final String SERIAL_NUMBER = "serialNumber"; private static final String FRIENDLY_NAME = "friendlyName"; private static final String UDN = "udn"; - private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI, - MODEL_NAME, MODEL_NUMBER, SERIAL_NUMBER, FRIENDLY_NAME, UDN); + private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URL, + MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URL, SERIAL_NUMBER, FRIENDLY_NAME); private final Logger logger = LoggerFactory.getLogger(UpnpAddonSuggestionFinder.class); private final Map devices = new ConcurrentHashMap<>(); private final UpnpService upnpService; + private static void logProperty(List propertyList, String name, @Nullable String value) { + if (value != null) { + propertyList.add(name + "=" + value); + } + } + @Activate public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; @@ -72,19 +84,43 @@ public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { } public void addDevice(@Nullable RemoteDevice device) { - if (device != null) { - String udnString = device.getIdentity().getUdn().getIdentifierString(); - devices.put(udnString, device); - if (logger.isTraceEnabled()) { - DeviceDetails deviceDetails = device.getDetails(); - ManufacturerDetails manufacturerDetails = deviceDetails.getManufacturerDetails(); - ModelDetails modelDetails = deviceDetails.getModelDetails(); - logger.trace( - "UPnP device type:{}, manufacturer:{}, manufacturerURI:{}, modelName:{}, modelNumber:{}, serialNumber:{}, friendlyName:{}, UDN:{}", - device.getType(), manufacturerDetails.getManufacturer(), - manufacturerDetails.getManufacturerURI().toString(), modelDetails.getModelName(), - modelDetails.getModelNumber(), deviceDetails.getSerialNumber(), deviceDetails.getFriendlyName(), - udnString); + if (device != null && device.getIdentity() != null && device.getIdentity().getUdn() != null) { + String udn = device.getIdentity().getUdn().getIdentifierString(); + if (devices.put(udn, device) == null && logger.isTraceEnabled()) { + List properties = new ArrayList<>(); + logProperty(properties, UDN, udn); + + DeviceType devType = device.getType(); + if (devType != null) { + logProperty(properties, DEVICE_TYPE, devType.getType()); + } + + DeviceDetails devDetails = device.getDetails(); + if (devDetails != null) { + logProperty(properties, SERIAL_NUMBER, devDetails.getSerialNumber()); + logProperty(properties, FRIENDLY_NAME, devDetails.getFriendlyName()); + + ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); + if (mfrDetails != null) { + URI mfrUri = mfrDetails.getManufacturerURI(); + logProperty(properties, MANUFACTURER, mfrDetails.getManufacturer()); + if (mfrUri != null) { + logProperty(properties, MANUFACTURER_URL, mfrUri.toString()); + } + } + + ModelDetails modDetails = devDetails.getModelDetails(); + if (modDetails != null) { + URI modUri = modDetails.getModelURI(); + logProperty(properties, MODEL_NAME, modDetails.getModelName()); + logProperty(properties, MODEL_NUMBER, modDetails.getModelNumber()); + logProperty(properties, MODEL_DESCRIPTION, modDetails.getModelDescription()); + if (modUri != null) { + logProperty(properties, MODEL_URL, modUri.toString()); + } + } + } + logger.trace("UPnP device properties={}", properties); } } } @@ -111,21 +147,54 @@ public Set getSuggestedAddons() { candidate.getUID(), String.join(",", propNames)); } else { devices.values().stream().forEach(device -> { - DeviceDetails deviceDetails = device.getDetails(); - ManufacturerDetails manufacturerDetails = deviceDetails.getManufacturerDetails(); - ModelDetails modelDetails = deviceDetails.getModelDetails(); - if (propertyMatches(map, DEVICE_TYPE, device.getType().getType()) - && propertyMatches(map, MANUFACTURER, manufacturerDetails.getManufacturer()) - && propertyMatches(map, MANUFACTURER_URI, - manufacturerDetails.getManufacturerURI().toString()) - && propertyMatches(map, MODEL_NAME, modelDetails.getModelName()) - && propertyMatches(map, MODEL_NUMBER, modelDetails.getModelNumber()) - && propertyMatches(map, SERIAL_NUMBER, deviceDetails.getSerialNumber()) - && propertyMatches(map, FRIENDLY_NAME, deviceDetails.getFriendlyName()) - && propertyMatches(map, UDN, - device.getIdentity().getUdn().getIdentifierString())) { + String deviceType = null; + String serialNumber = null; + String friendlyName = null; + String manufacturer = null; + String manufacturerURL = null; + String modelName = null; + String modelNumber = null; + String modelDescription = null; + String modelURL = null; + + DeviceType devType = device.getType(); + if (devType != null) { + deviceType = devType.getType(); + } + + DeviceDetails devDetails = device.getDetails(); + if (devDetails != null) { + friendlyName = devDetails.getFriendlyName(); + serialNumber = devDetails.getSerialNumber(); + + ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); + if (mfrDetails != null) { + URI mfrUri = mfrDetails.getManufacturerURI(); + manufacturer = mfrDetails.getManufacturer(); + manufacturerURL = mfrUri != null ? mfrUri.toString() : null; + } + + ModelDetails modDetails = devDetails.getModelDetails(); + if (modDetails != null) { + URI modUri = modDetails.getModelURI(); + modelName = modDetails.getModelName(); + modelDescription = modDetails.getModelDescription(); + modelNumber = modDetails.getModelNumber(); + modelURL = modUri != null ? modUri.toString() : null; + } + } + + if (propertyMatches(map, DEVICE_TYPE, deviceType) + && propertyMatches(map, MANUFACTURER, manufacturer) + && propertyMatches(map, MANUFACTURER_URL, manufacturerURL) + && propertyMatches(map, MODEL_NAME, modelName) + && propertyMatches(map, MODEL_NUMBER, modelNumber) + && propertyMatches(map, MODEL_DESCRIPTION, modelDescription) + && propertyMatches(map, MODEL_URL, modelURL) + && propertyMatches(map, SERIAL_NUMBER, serialNumber) + && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { result.add(candidate); - logger.debug("Addon '{}' will be suggested", candidate.getUID()); + logger.debug("Addon '{}' will be suggested (via UPnP)", candidate.getUID()); } }); } From 679ec1bc21f00db48f81f07af7f4dee4da2f2b99 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 31 Oct 2023 09:49:24 +0000 Subject: [PATCH 45/98] [suggested addon finder] quick fixes for reviewer Signed-off-by: Andrew Fiddian-Green --- .../finders/BaseAddonSuggestionFinder.java | 1 + .../finders/MDNSAddonSuggestionFinder.java | 15 ++++++++----- .../finders/UpnpAddonSuggestionFinder.java | 22 +++++++++---------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index 6f777ad9b0c..b4ee8fa82b0 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -58,6 +58,7 @@ public void close() { public abstract Set getSuggestedAddons(); public void setAddonCandidates(List candidates) { + addonCandidates.clear(); addonCandidates.addAll(candidates); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 8c473679588..bf4dfa8d387 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -82,15 +82,18 @@ public Set getSuggestedAddons() { addonCandidates.forEach(candidate -> { candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) .forEach(method -> { - Map map = method.getMatchProperties().stream().collect( + Map matchProperties = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); services.values().stream().forEach(service -> { if (method.getMdnsServiceType().equals(service.getType()) - && propertyMatches(map, NAME, service.getName()) - && Collections.list(service.getPropertyNames()).stream().allMatch( - name -> propertyMatches(map, name, service.getPropertyString(name)))) { - result.add(candidate); - logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); + && propertyMatches(matchProperties, NAME, service.getName())) { + List serviceProperties = Collections.list(service.getPropertyNames()); + if (serviceProperties.containsAll(matchProperties.keySet()) + && serviceProperties.stream().allMatch(name -> propertyMatches(matchProperties, + name, service.getPropertyString(name)))) { + result.add(candidate); + logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); + } } }); }); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index d3f652a5aad..805ddbd9930 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -137,10 +137,10 @@ public Set getSuggestedAddons() { addonCandidates.forEach(candidate -> { candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) .forEach(method -> { - Map map = method.getMatchProperties().stream().collect( + Map matchProperties = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); - Set propNames = new HashSet<>(map.keySet()); + Set propNames = new HashSet<>(matchProperties.keySet()); propNames.removeAll(SUPPORTED_PROPERTIES); if (!propNames.isEmpty()) { logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", @@ -184,15 +184,15 @@ public Set getSuggestedAddons() { } } - if (propertyMatches(map, DEVICE_TYPE, deviceType) - && propertyMatches(map, MANUFACTURER, manufacturer) - && propertyMatches(map, MANUFACTURER_URL, manufacturerURL) - && propertyMatches(map, MODEL_NAME, modelName) - && propertyMatches(map, MODEL_NUMBER, modelNumber) - && propertyMatches(map, MODEL_DESCRIPTION, modelDescription) - && propertyMatches(map, MODEL_URL, modelURL) - && propertyMatches(map, SERIAL_NUMBER, serialNumber) - && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { + if (propertyMatches(matchProperties, DEVICE_TYPE, deviceType) + && propertyMatches(matchProperties, MANUFACTURER, manufacturer) + && propertyMatches(matchProperties, MANUFACTURER_URL, manufacturerURL) + && propertyMatches(matchProperties, MODEL_NAME, modelName) + && propertyMatches(matchProperties, MODEL_NUMBER, modelNumber) + && propertyMatches(matchProperties, MODEL_DESCRIPTION, modelDescription) + && propertyMatches(matchProperties, MODEL_URL, modelURL) + && propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber) + && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { result.add(candidate); logger.debug("Addon '{}' will be suggested (via UPnP)", candidate.getUID()); } From 2dfe987d2dbdc4593502a387bb5d85a69f1f9d36 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 31 Oct 2023 11:23:52 +0000 Subject: [PATCH 46/98] [suggested addon finder] update candidates on load Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionFinderService.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 3cd133daca7..24e5a23d537 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -26,6 +26,7 @@ import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.config.discovery.addon.finders.AddonSuggestionFinder; +import org.openhab.core.i18n.LocaleProvider; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -46,18 +47,28 @@ public class AddonSuggestionFinderService implements AutoCloseable { private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); private final List addonSuggestionFinders = Collections.synchronizedList(new ArrayList<>()); + private final LocaleProvider localeProvider; @Activate - public AddonSuggestionFinderService() { + public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { + this.localeProvider = localeProvider; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); + addonInfoProvidersChanged(); } public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.remove(addonInfoProvider); + addonInfoProvidersChanged(); + } + + private void addonInfoProvidersChanged() { + List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) + .flatMap(Collection::stream).toList(); + addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -77,9 +88,6 @@ public void close() throws Exception { } public Set getSuggestedAddons(@Nullable Locale locale) { - List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(locale)) - .flatMap(Collection::stream).toList(); - addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); return addonSuggestionFinders.stream().map(f -> f.getSuggestedAddons()).flatMap(Collection::stream) .collect(Collectors.toSet()); } From 632ae4a6f093fee9de6d8655f69a0d99fb46625c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 31 Oct 2023 12:44:34 +0000 Subject: [PATCH 47/98] [suggested addon finder] fix check for null properties Signed-off-by: Andrew Fiddian-Green --- .../finders/MDNSAddonSuggestionFinder.java | 16 ++++++-------- .../finders/UpnpAddonSuggestionFinder.java | 22 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index bf4dfa8d387..0fc50e91013 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -82,18 +82,16 @@ public Set getSuggestedAddons() { addonCandidates.forEach(candidate -> { candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) .forEach(method -> { - Map matchProperties = method.getMatchProperties().stream().collect( + Map map = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + services.values().stream().forEach(service -> { if (method.getMdnsServiceType().equals(service.getType()) - && propertyMatches(matchProperties, NAME, service.getName())) { - List serviceProperties = Collections.list(service.getPropertyNames()); - if (serviceProperties.containsAll(matchProperties.keySet()) - && serviceProperties.stream().allMatch(name -> propertyMatches(matchProperties, - name, service.getPropertyString(name)))) { - result.add(candidate); - logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); - } + && propertyMatches(map, NAME, service.getName()) + && map.keySet().stream().filter(name -> !NAME.equals(name)).allMatch( + name -> propertyMatches(map, name, service.getPropertyString(name)))) { + result.add(candidate); + logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); } }); }); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 805ddbd9930..d3f652a5aad 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -137,10 +137,10 @@ public Set getSuggestedAddons() { addonCandidates.forEach(candidate -> { candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) .forEach(method -> { - Map matchProperties = method.getMatchProperties().stream().collect( + Map map = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); - Set propNames = new HashSet<>(matchProperties.keySet()); + Set propNames = new HashSet<>(map.keySet()); propNames.removeAll(SUPPORTED_PROPERTIES); if (!propNames.isEmpty()) { logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", @@ -184,15 +184,15 @@ public Set getSuggestedAddons() { } } - if (propertyMatches(matchProperties, DEVICE_TYPE, deviceType) - && propertyMatches(matchProperties, MANUFACTURER, manufacturer) - && propertyMatches(matchProperties, MANUFACTURER_URL, manufacturerURL) - && propertyMatches(matchProperties, MODEL_NAME, modelName) - && propertyMatches(matchProperties, MODEL_NUMBER, modelNumber) - && propertyMatches(matchProperties, MODEL_DESCRIPTION, modelDescription) - && propertyMatches(matchProperties, MODEL_URL, modelURL) - && propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber) - && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { + if (propertyMatches(map, DEVICE_TYPE, deviceType) + && propertyMatches(map, MANUFACTURER, manufacturer) + && propertyMatches(map, MANUFACTURER_URL, manufacturerURL) + && propertyMatches(map, MODEL_NAME, modelName) + && propertyMatches(map, MODEL_NUMBER, modelNumber) + && propertyMatches(map, MODEL_DESCRIPTION, modelDescription) + && propertyMatches(map, MODEL_URL, modelURL) + && propertyMatches(map, SERIAL_NUMBER, serialNumber) + && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { result.add(candidate); logger.debug("Addon '{}' will be suggested (via UPnP)", candidate.getUID()); } From e6005799cd7cbe68ed2b1490041e7426b0673a50 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 1 Nov 2023 16:26:42 +0000 Subject: [PATCH 48/98] [suggested addon finder] works in progress Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/addon/AddonInfo.java | 23 +++-- .../openhab/core/addon/AddonInfoRegistry.java | 4 + .../finders/MDNSAddonSuggestionFinder.java | 22 ++--- .../finders/UpnpAddonSuggestionFinder.java | 92 ++++++++++--------- .../AddonSuggestionFinderServiceTests.java | 2 +- 5 files changed, 80 insertions(+), 63 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index a6d29ec3c61..8e45c47c918 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -37,6 +37,7 @@ public class AddonInfo implements Identifiable { private final String id; private final String type; + private final String uid; private final String name; private final String description; private final @Nullable String connection; @@ -46,10 +47,10 @@ public class AddonInfo implements Identifiable { private @Nullable String sourceBundle; private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, String name, String description, @Nullable String connection, - List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, - @Nullable String sourceBundle, @Nullable List discoveryMethods) - throws IllegalArgumentException { + private AddonInfo(String id, String type, @Nullable String uid, String name, String description, + @Nullable String connection, List countries, @Nullable String configDescriptionURI, + @Nullable String serviceId, @Nullable String sourceBundle, + @Nullable List discoveryMethods) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -66,6 +67,7 @@ private AddonInfo(String id, String type, String name, String description, @Null } this.id = id; this.type = type; + this.uid = uid != null ? uid : type + Addon.ADDON_SEPARATOR + id; this.name = name; this.description = description; @@ -85,7 +87,7 @@ private AddonInfo(String id, String type, String name, String description, @Null */ @Override public String getUID() { - return type + Addon.ADDON_SEPARATOR + id; + return uid; } /** @@ -162,6 +164,7 @@ public static class Builder { private final String id; private final String type; + private @Nullable String uid; private String name = ""; private String description = ""; private @Nullable String connection; @@ -179,6 +182,7 @@ private Builder(String id, String type) { private Builder(AddonInfo addonInfo) { this.id = addonInfo.id; this.type = addonInfo.type; + this.uid = addonInfo.uid; this.name = addonInfo.name; this.description = addonInfo.description; this.connection = addonInfo.connection; @@ -189,6 +193,11 @@ private Builder(AddonInfo addonInfo) { this.discoveryMethods = addonInfo.discoveryMethods; } + public Builder withUID(@Nullable String uid) { + this.uid = uid; + return this; + } + public Builder withName(String name) { this.name = name; return this; @@ -241,8 +250,8 @@ public Builder withDiscoveryMethods(@Nullable List discove * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, - sourceBundle, discoveryMethods); + return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, + serviceId, sourceBundle, discoveryMethods); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 08f82b05f34..7b4c1e21df0 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -119,6 +119,10 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) { builder.withServiceId(b.getServiceId()); } + String defaultUID = a.getType() + Addon.ADDON_SEPARATOR + a.getId(); + if (defaultUID.equals(a.getUID()) && !defaultUID.equals(b.getUID())) { + builder.withUID(b.getUID()); + } Set discoveryMethods = new HashSet<>(a.getDiscoveryMethods()); discoveryMethods.addAll(b.getDiscoveryMethods()); if (!discoveryMethods.isEmpty()) { diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 0fc50e91013..b4c55d85151 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -13,6 +13,7 @@ package org.openhab.core.config.discovery.addon.finders; import java.util.Collections; +import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -59,14 +60,16 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } - public void addService(@Nullable ServiceInfo service) { - if (service != null) { - String qualifiedName = service.getQualifiedName(); - if (services.put(qualifiedName, service) == null && logger.isTraceEnabled()) { - logger.trace("mDNS service name={}, properties={}", qualifiedName, - Collections.list(service.getPropertyNames()).stream() - .map(n -> n + "=" + service.getPropertyString(n)).toList()); + public void addService(ServiceInfo service) { + String qualifiedName = service.getQualifiedName(); + if (services.put(qualifiedName, service) == null && logger.isTraceEnabled()) { + List properties = List.of("name=" + qualifiedName); + Enumeration enumeration = service.getPropertyNames(); + if (enumeration != null) { + properties.addAll(Collections.list(enumeration).stream() + .map(n -> n + "=" + service.getPropertyString(n)).toList()); } + logger.trace("mDNS service {}", properties); } } @@ -101,9 +104,6 @@ && propertyMatches(map, NAME, service.getName()) @Override public void serviceAdded(@Nullable ServiceEvent event) { - if (event != null) { - addService(event.getInfo()); - } } @Override @@ -112,7 +112,7 @@ public void serviceRemoved(@Nullable ServiceEvent event) { @Override public void serviceResolved(@Nullable ServiceEvent event) { - if (event != null) { + if (event != null && event.getInfo() != null) { addService(event.getInfo()); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index d3f652a5aad..770093dd152 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -30,6 +30,7 @@ import org.jupnp.model.meta.ManufacturerDetails; import org.jupnp.model.meta.ModelDetails; import org.jupnp.model.meta.RemoteDevice; +import org.jupnp.model.meta.RemoteDeviceIdentity; import org.jupnp.model.types.DeviceType; import org.jupnp.registry.Registry; import org.jupnp.registry.RegistryListener; @@ -55,17 +56,17 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private static final String DEVICE_TYPE = "deviceType"; private static final String MANUFACTURER = "manufacturer"; - private static final String MANUFACTURER_URL = "manufacturerURL"; + private static final String MANUFACTURER_URI = "manufacturerURI"; private static final String MODEL_NAME = "modelName"; private static final String MODEL_NUMBER = "modelNumber"; private static final String MODEL_DESCRIPTION = "modelDescription"; - private static final String MODEL_URL = "modelURL"; + private static final String MODEL_URI = "modelURI"; private static final String SERIAL_NUMBER = "serialNumber"; private static final String FRIENDLY_NAME = "friendlyName"; private static final String UDN = "udn"; - private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URL, - MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URL, SERIAL_NUMBER, FRIENDLY_NAME); + private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI, + MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URI, SERIAL_NUMBER, FRIENDLY_NAME); private final Logger logger = LoggerFactory.getLogger(UpnpAddonSuggestionFinder.class); private final Map devices = new ConcurrentHashMap<>(); @@ -83,45 +84,44 @@ public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService.getRegistry().addListener(this); } - public void addDevice(@Nullable RemoteDevice device) { - if (device != null && device.getIdentity() != null && device.getIdentity().getUdn() != null) { - String udn = device.getIdentity().getUdn().getIdentifierString(); - if (devices.put(udn, device) == null && logger.isTraceEnabled()) { - List properties = new ArrayList<>(); - logProperty(properties, UDN, udn); + public void addDevice(RemoteDevice device) { + RemoteDeviceIdentity identity = device.getIdentity(); + String udn = identity != null ? udn = identity.getUdn().getIdentifierString() : "NULL"; + if (devices.put(udn, device) == null && logger.isTraceEnabled()) { + List properties = new ArrayList<>(); + logProperty(properties, UDN, udn); - DeviceType devType = device.getType(); - if (devType != null) { - logProperty(properties, DEVICE_TYPE, devType.getType()); - } + DeviceType devType = device.getType(); + if (devType != null) { + logProperty(properties, DEVICE_TYPE, devType.getType()); + } - DeviceDetails devDetails = device.getDetails(); - if (devDetails != null) { - logProperty(properties, SERIAL_NUMBER, devDetails.getSerialNumber()); - logProperty(properties, FRIENDLY_NAME, devDetails.getFriendlyName()); - - ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); - if (mfrDetails != null) { - URI mfrUri = mfrDetails.getManufacturerURI(); - logProperty(properties, MANUFACTURER, mfrDetails.getManufacturer()); - if (mfrUri != null) { - logProperty(properties, MANUFACTURER_URL, mfrUri.toString()); - } + DeviceDetails devDetails = device.getDetails(); + if (devDetails != null) { + logProperty(properties, SERIAL_NUMBER, devDetails.getSerialNumber()); + logProperty(properties, FRIENDLY_NAME, devDetails.getFriendlyName()); + + ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); + if (mfrDetails != null) { + URI mfrUri = mfrDetails.getManufacturerURI(); + logProperty(properties, MANUFACTURER, mfrDetails.getManufacturer()); + if (mfrUri != null) { + logProperty(properties, MANUFACTURER_URI, mfrUri.toString()); } + } - ModelDetails modDetails = devDetails.getModelDetails(); - if (modDetails != null) { - URI modUri = modDetails.getModelURI(); - logProperty(properties, MODEL_NAME, modDetails.getModelName()); - logProperty(properties, MODEL_NUMBER, modDetails.getModelNumber()); - logProperty(properties, MODEL_DESCRIPTION, modDetails.getModelDescription()); - if (modUri != null) { - logProperty(properties, MODEL_URL, modUri.toString()); - } + ModelDetails modDetails = devDetails.getModelDetails(); + if (modDetails != null) { + URI modUri = modDetails.getModelURI(); + logProperty(properties, MODEL_NAME, modDetails.getModelName()); + logProperty(properties, MODEL_NUMBER, modDetails.getModelNumber()); + logProperty(properties, MODEL_DESCRIPTION, modDetails.getModelDescription()); + if (modUri != null) { + logProperty(properties, MODEL_URI, modUri.toString()); } } - logger.trace("UPnP device properties={}", properties); } + logger.trace("UPnP device {}", properties); } } @@ -151,11 +151,11 @@ public Set getSuggestedAddons() { String serialNumber = null; String friendlyName = null; String manufacturer = null; - String manufacturerURL = null; + String manufacturerURI = null; String modelName = null; String modelNumber = null; String modelDescription = null; - String modelURL = null; + String modelURI = null; DeviceType devType = device.getType(); if (devType != null) { @@ -171,7 +171,7 @@ public Set getSuggestedAddons() { if (mfrDetails != null) { URI mfrUri = mfrDetails.getManufacturerURI(); manufacturer = mfrDetails.getManufacturer(); - manufacturerURL = mfrUri != null ? mfrUri.toString() : null; + manufacturerURI = mfrUri != null ? mfrUri.toString() : null; } ModelDetails modDetails = devDetails.getModelDetails(); @@ -180,17 +180,17 @@ public Set getSuggestedAddons() { modelName = modDetails.getModelName(); modelDescription = modDetails.getModelDescription(); modelNumber = modDetails.getModelNumber(); - modelURL = modUri != null ? modUri.toString() : null; + modelURI = modUri != null ? modUri.toString() : null; } } if (propertyMatches(map, DEVICE_TYPE, deviceType) && propertyMatches(map, MANUFACTURER, manufacturer) - && propertyMatches(map, MANUFACTURER_URL, manufacturerURL) + && propertyMatches(map, MANUFACTURER_URI, manufacturerURI) && propertyMatches(map, MODEL_NAME, modelName) && propertyMatches(map, MODEL_NUMBER, modelNumber) && propertyMatches(map, MODEL_DESCRIPTION, modelDescription) - && propertyMatches(map, MODEL_URL, modelURL) + && propertyMatches(map, MODEL_URI, modelURI) && propertyMatches(map, SERIAL_NUMBER, serialNumber) && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { result.add(candidate); @@ -221,7 +221,9 @@ public void localDeviceRemoved(@Nullable Registry registry, @Nullable LocalDevic @Override public void remoteDeviceAdded(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { - addDevice(remoteDevice); + if (remoteDevice != null) { + addDevice(remoteDevice); + } } @Override @@ -239,6 +241,8 @@ public void remoteDeviceRemoved(@Nullable Registry registry, @Nullable RemoteDev @Override public void remoteDeviceUpdated(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) { - addDevice(remoteDevice); + if (remoteDevice != null) { + addDevice(remoteDevice); + } } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index ba4b078e68d..e9855956078 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -101,7 +101,7 @@ public void setup() { } private void createAddonSuggestionFinderService() { - addonSuggestionFinderService = new AddonSuggestionFinderService(); + addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider); assertNotNull(addonSuggestionFinderService); UpnpAddonSuggestionFinder upnp = new UpnpAddonSuggestionFinder(upnpService); From e4662404a51f386b7222260cf32d65032ee60e19 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 2 Nov 2023 12:30:20 +0000 Subject: [PATCH 49/98] [suggested addon finder] remove trace logging Signed-off-by: Andrew Fiddian-Green --- .../finders/MDNSAddonSuggestionFinder.java | 32 +++++------ .../finders/UpnpAddonSuggestionFinder.java | 55 +++---------------- 2 files changed, 21 insertions(+), 66 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index b4c55d85151..807407b60e7 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -12,8 +12,6 @@ */ package org.openhab.core.config.discovery.addon.finders; -import java.util.Collections; -import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -61,16 +59,7 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { } public void addService(ServiceInfo service) { - String qualifiedName = service.getQualifiedName(); - if (services.put(qualifiedName, service) == null && logger.isTraceEnabled()) { - List properties = List.of("name=" + qualifiedName); - Enumeration enumeration = service.getPropertyNames(); - if (enumeration != null) { - properties.addAll(Collections.list(enumeration).stream() - .map(n -> n + "=" + service.getPropertyString(n)).toList()); - } - logger.trace("mDNS service {}", properties); - } + services.put(service.getQualifiedName(), service); } @Deactivate @@ -85,16 +74,18 @@ public Set getSuggestedAddons() { addonCandidates.forEach(candidate -> { candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) .forEach(method -> { - Map map = method.getMatchProperties().stream().collect( + Map matchProperties = method.getMatchProperties().stream().collect( Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + Set matchPropertyKeys = matchProperties.keySet().stream() + .filter(property -> !NAME.equals(property)).collect(Collectors.toSet()); services.values().stream().forEach(service -> { if (method.getMdnsServiceType().equals(service.getType()) - && propertyMatches(map, NAME, service.getName()) - && map.keySet().stream().filter(name -> !NAME.equals(name)).allMatch( - name -> propertyMatches(map, name, service.getPropertyString(name)))) { + && propertyMatches(matchProperties, NAME, service.getName()) + && matchPropertyKeys.stream().allMatch(name -> propertyMatches(matchProperties, + name, service.getPropertyString(name)))) { result.add(candidate); - logger.debug("Addon '{}' will be suggested (via mDNS)", candidate.getUID()); + logger.debug("Suggested addon found via mDNS: {}", candidate.getUID()); } }); }); @@ -112,8 +103,11 @@ public void serviceRemoved(@Nullable ServiceEvent event) { @Override public void serviceResolved(@Nullable ServiceEvent event) { - if (event != null && event.getInfo() != null) { - addService(event.getInfo()); + if (event != null) { + ServiceInfo service = event.getInfo(); + if (service != null) { + addService(service); + } } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 770093dd152..aa66f46745d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -13,9 +13,7 @@ package org.openhab.core.config.discovery.addon.finders; import java.net.URI; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -32,6 +30,7 @@ import org.jupnp.model.meta.RemoteDevice; import org.jupnp.model.meta.RemoteDeviceIdentity; import org.jupnp.model.types.DeviceType; +import org.jupnp.model.types.UDN; import org.jupnp.registry.Registry; import org.jupnp.registry.RegistryListener; import org.openhab.core.addon.AddonInfo; @@ -63,7 +62,6 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private static final String MODEL_URI = "modelURI"; private static final String SERIAL_NUMBER = "serialNumber"; private static final String FRIENDLY_NAME = "friendlyName"; - private static final String UDN = "udn"; private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI, MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URI, SERIAL_NUMBER, FRIENDLY_NAME); @@ -72,56 +70,19 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private final Map devices = new ConcurrentHashMap<>(); private final UpnpService upnpService; - private static void logProperty(List propertyList, String name, @Nullable String value) { - if (value != null) { - propertyList.add(name + "=" + value); - } - } - @Activate public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; this.upnpService.getRegistry().addListener(this); } - public void addDevice(RemoteDevice device) { - RemoteDeviceIdentity identity = device.getIdentity(); - String udn = identity != null ? udn = identity.getUdn().getIdentifierString() : "NULL"; - if (devices.put(udn, device) == null && logger.isTraceEnabled()) { - List properties = new ArrayList<>(); - logProperty(properties, UDN, udn); - - DeviceType devType = device.getType(); - if (devType != null) { - logProperty(properties, DEVICE_TYPE, devType.getType()); - } - - DeviceDetails devDetails = device.getDetails(); - if (devDetails != null) { - logProperty(properties, SERIAL_NUMBER, devDetails.getSerialNumber()); - logProperty(properties, FRIENDLY_NAME, devDetails.getFriendlyName()); - - ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); - if (mfrDetails != null) { - URI mfrUri = mfrDetails.getManufacturerURI(); - logProperty(properties, MANUFACTURER, mfrDetails.getManufacturer()); - if (mfrUri != null) { - logProperty(properties, MANUFACTURER_URI, mfrUri.toString()); - } - } - - ModelDetails modDetails = devDetails.getModelDetails(); - if (modDetails != null) { - URI modUri = modDetails.getModelURI(); - logProperty(properties, MODEL_NAME, modDetails.getModelName()); - logProperty(properties, MODEL_NUMBER, modDetails.getModelNumber()); - logProperty(properties, MODEL_DESCRIPTION, modDetails.getModelDescription()); - if (modUri != null) { - logProperty(properties, MODEL_URI, modUri.toString()); - } - } + public void addDevice(RemoteDevice remoteDevice) { + RemoteDeviceIdentity identity = remoteDevice.getIdentity(); + if (identity != null) { + UDN udn = identity.getUdn(); + if (udn != null) { + devices.put(udn.getIdentifierString(), remoteDevice); } - logger.trace("UPnP device {}", properties); } } @@ -194,7 +155,7 @@ && propertyMatches(map, MODEL_URI, modelURI) && propertyMatches(map, SERIAL_NUMBER, serialNumber) && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { result.add(candidate); - logger.debug("Addon '{}' will be suggested (via UPnP)", candidate.getUID()); + logger.debug("Suggested addon found via UPnP: {}", candidate.getUID()); } }); } From e32a0d798894d00433566b1f946d2ccbbd4a2bd4 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 4 Nov 2023 18:06:34 +0000 Subject: [PATCH 50/98] [suggested addon finder] performance tweaks Signed-off-by: Andrew Fiddian-Green --- .../finders/BaseAddonSuggestionFinder.java | 12 +- .../finders/MDNSAddonSuggestionFinder.java | 85 +++++++--- .../finders/UpnpAddonSuggestionFinder.java | 157 ++++++++++-------- 3 files changed, 154 insertions(+), 100 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index b4ee8fa82b0..b17d459321d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -46,19 +46,23 @@ public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder protected static boolean propertyMatches(Map propertyPatternMap, String propertyName, @Nullable String propertyValue) { Pattern pattern = propertyPatternMap.get(propertyName); - return pattern == null ? true : propertyValue != null ? pattern.matcher(propertyValue).matches() : false; + return pattern == null ? true : propertyValue == null ? false : pattern.matcher(propertyValue).matches(); } protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - public void close() { - addonCandidates.clear(); + protected void deactivate() { + resetAddonCandidates(); } public abstract Set getSuggestedAddons(); - public void setAddonCandidates(List candidates) { + protected void resetAddonCandidates() { addonCandidates.clear(); + } + + public void setAddonCandidates(List candidates) { + resetAddonCandidates(); addonCandidates.addAll(candidates); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 807407b60e7..6905084c604 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -26,7 +27,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; +import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -48,8 +51,10 @@ public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; private static final String NAME = "name"; + private static final String APPLICATION = "application"; private final Logger logger = LoggerFactory.getLogger(MDNSAddonSuggestionFinder.class); + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Map services = new ConcurrentHashMap<>(); private final MDNSClient mdnsClient; @@ -58,43 +63,61 @@ public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } - public void addService(ServiceInfo service) { - services.put(service.getQualifiedName(), service); + public void addService(ServiceInfo service, boolean isResolved) { + String qualifiedName = service.getQualifiedName(); + if (isResolved || !services.containsKey(qualifiedName)) { + services.put(qualifiedName, service); + logger.trace("Added service: {}/{}", qualifiedName, service.getNiceTextString()); + } } @Deactivate - public void close() { + @Override + protected void deactivate() { services.clear(); - super.close(); + super.deactivate(); } @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); - addonCandidates.forEach(candidate -> { - candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) - .forEach(method -> { - Map matchProperties = method.getMatchProperties().stream().collect( - Collectors.toMap(property -> property.getName(), property -> property.getPattern())); - Set matchPropertyKeys = matchProperties.keySet().stream() - .filter(property -> !NAME.equals(property)).collect(Collectors.toSet()); - - services.values().stream().forEach(service -> { - if (method.getMdnsServiceType().equals(service.getType()) - && propertyMatches(matchProperties, NAME, service.getName()) - && matchPropertyKeys.stream().allMatch(name -> propertyMatches(matchProperties, - name, service.getPropertyString(name)))) { - result.add(candidate); - logger.debug("Suggested addon found via mDNS: {}", candidate.getUID()); - } - }); - }); - }); + for (AddonInfo candidate : addonCandidates) { + for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream() + .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) { + Map matchProperties = method.getMatchProperties().stream() + .collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + + Set matchPropertyKeys = matchProperties.keySet().stream() + .filter(property -> (!NAME.equals(property) && !APPLICATION.equals(property))) + .collect(Collectors.toSet()); + + logger.trace("Checking candidate: {}", candidate.getUID()); + for (ServiceInfo service : services.values()) { + + logger.trace("Checking service: {}/{}", service.getQualifiedName(), service.getNiceTextString()); + if (method.getMdnsServiceType().equals(service.getType()) + && propertyMatches(matchProperties, NAME, service.getName()) + && propertyMatches(matchProperties, APPLICATION, service.getApplication()) + && matchPropertyKeys.stream().allMatch( + name -> propertyMatches(matchProperties, name, service.getPropertyString(name)))) { + result.add(candidate); + logger.debug("Suggested addon found: {}", candidate.getUID()); + break; + } + } + } + } return result; } @Override public void serviceAdded(@Nullable ServiceEvent event) { + if (event != null) { + ServiceInfo service = event.getInfo(); + if (service != null) { + addService(service, false); + } + } } @Override @@ -106,15 +129,27 @@ public void serviceResolved(@Nullable ServiceEvent event) { if (event != null) { ServiceInfo service = event.getInfo(); if (service != null) { - addService(service); + addService(service, true); } } } + @Override + protected void resetAddonCandidates() { + addonCandidates.forEach(c -> c.getDiscoveryMethods().stream() + .filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty()) + .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); + super.resetAddonCandidates(); + } + @Override public void setAddonCandidates(List candidates) { super.setAddonCandidates(candidates); candidates.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) - .forEach(m -> mdnsClient.addServiceListener(m.getMdnsServiceType(), this))); + .filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> { + String serviceType = m.getMdnsServiceType(); + mdnsClient.addServiceListener(serviceType, this); + scheduler.submit(() -> mdnsClient.list(serviceType)); + })); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index aa66f46745d..fe9c7c712e1 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -33,6 +33,7 @@ import org.jupnp.model.types.UDN; import org.jupnp.registry.Registry; import org.jupnp.registry.RegistryListener; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -73,94 +74,108 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme @Activate public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; - this.upnpService.getRegistry().addListener(this); + Registry registry = upnpService.getRegistry(); + for (RemoteDevice device : registry.getRemoteDevices()) { + remoteDeviceAdded(registry, device); + } + registry.addListener(this); } - public void addDevice(RemoteDevice remoteDevice) { - RemoteDeviceIdentity identity = remoteDevice.getIdentity(); + public void addDevice(RemoteDevice device) { + RemoteDeviceIdentity identity = device.getIdentity(); if (identity != null) { UDN udn = identity.getUdn(); if (udn != null) { - devices.put(udn.getIdentifierString(), remoteDevice); + String udnString = udn.getIdentifierString(); + devices.put(udnString, device); + logger.trace("Added device: {}", device.getDisplayString()); } } } @Deactivate - public void close() { + @Override + protected void deactivate() { devices.clear(); - super.close(); + upnpService.getRegistry().removeListener(this); + super.deactivate(); } @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); - addonCandidates.forEach(candidate -> { - candidate.getDiscoveryMethods().stream().filter(method -> SERVICE_TYPE.equals(method.getServiceType())) - .forEach(method -> { - Map map = method.getMatchProperties().stream().collect( - Collectors.toMap(property -> property.getName(), property -> property.getPattern())); - - Set propNames = new HashSet<>(map.keySet()); - propNames.removeAll(SUPPORTED_PROPERTIES); - if (!propNames.isEmpty()) { - logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", - candidate.getUID(), String.join(",", propNames)); - } else { - devices.values().stream().forEach(device -> { - String deviceType = null; - String serialNumber = null; - String friendlyName = null; - String manufacturer = null; - String manufacturerURI = null; - String modelName = null; - String modelNumber = null; - String modelDescription = null; - String modelURI = null; - - DeviceType devType = device.getType(); - if (devType != null) { - deviceType = devType.getType(); - } - - DeviceDetails devDetails = device.getDetails(); - if (devDetails != null) { - friendlyName = devDetails.getFriendlyName(); - serialNumber = devDetails.getSerialNumber(); - - ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); - if (mfrDetails != null) { - URI mfrUri = mfrDetails.getManufacturerURI(); - manufacturer = mfrDetails.getManufacturer(); - manufacturerURI = mfrUri != null ? mfrUri.toString() : null; - } - - ModelDetails modDetails = devDetails.getModelDetails(); - if (modDetails != null) { - URI modUri = modDetails.getModelURI(); - modelName = modDetails.getModelName(); - modelDescription = modDetails.getModelDescription(); - modelNumber = modDetails.getModelNumber(); - modelURI = modUri != null ? modUri.toString() : null; - } - } - - if (propertyMatches(map, DEVICE_TYPE, deviceType) - && propertyMatches(map, MANUFACTURER, manufacturer) - && propertyMatches(map, MANUFACTURER_URI, manufacturerURI) - && propertyMatches(map, MODEL_NAME, modelName) - && propertyMatches(map, MODEL_NUMBER, modelNumber) - && propertyMatches(map, MODEL_DESCRIPTION, modelDescription) - && propertyMatches(map, MODEL_URI, modelURI) - && propertyMatches(map, SERIAL_NUMBER, serialNumber) - && propertyMatches(map, FRIENDLY_NAME, friendlyName)) { - result.add(candidate); - logger.debug("Suggested addon found via UPnP: {}", candidate.getUID()); - } - }); + for (AddonInfo candidate : addonCandidates) { + for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream() + .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) { + Map matchProperties = method.getMatchProperties().stream() + .collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern())); + + Set propertyNames = new HashSet<>(matchProperties.keySet()); + propertyNames.removeAll(SUPPORTED_PROPERTIES); + + if (!propertyNames.isEmpty()) { + logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", + candidate.getUID(), String.join(",", propertyNames)); + break; + } + + logger.trace("Checking candidate: {}", candidate.getUID()); + for (RemoteDevice device : devices.values()) { + + String deviceType = null; + String serialNumber = null; + String friendlyName = null; + String manufacturer = null; + String manufacturerURI = null; + String modelName = null; + String modelNumber = null; + String modelDescription = null; + String modelURI = null; + + DeviceType devType = device.getType(); + if (devType != null) { + deviceType = devType.getType(); + } + + DeviceDetails devDetails = device.getDetails(); + if (devDetails != null) { + friendlyName = devDetails.getFriendlyName(); + serialNumber = devDetails.getSerialNumber(); + + ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails(); + if (mfrDetails != null) { + URI mfrUri = mfrDetails.getManufacturerURI(); + manufacturer = mfrDetails.getManufacturer(); + manufacturerURI = mfrUri != null ? mfrUri.toString() : null; + } + + ModelDetails modDetails = devDetails.getModelDetails(); + if (modDetails != null) { + URI modUri = modDetails.getModelURI(); + modelName = modDetails.getModelName(); + modelDescription = modDetails.getModelDescription(); + modelNumber = modDetails.getModelNumber(); + modelURI = modUri != null ? modUri.toString() : null; } - }); - }); + } + + logger.trace("Checking device: {}", device.getDisplayString()); + if (propertyMatches(matchProperties, DEVICE_TYPE, deviceType) + && propertyMatches(matchProperties, MANUFACTURER, manufacturer) + && propertyMatches(matchProperties, MANUFACTURER_URI, manufacturerURI) + && propertyMatches(matchProperties, MODEL_NAME, modelName) + && propertyMatches(matchProperties, MODEL_NUMBER, modelNumber) + && propertyMatches(matchProperties, MODEL_DESCRIPTION, modelDescription) + && propertyMatches(matchProperties, MODEL_URI, modelURI) + && propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber) + && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { + result.add(candidate); + logger.debug("Suggested addon found: {}", candidate.getUID()); + break; + } + } + } + } return result; } From 2160dd3c825f0647f22600795f842c6ef41cedf9 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 4 Nov 2023 18:50:42 +0000 Subject: [PATCH 51/98] [suggested addon finder] fix test Signed-off-by: Andrew Fiddian-Green --- .../addon/tests/AddonSuggestionFinderServiceTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index e9855956078..5dd8905731b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -111,10 +111,10 @@ private void createAddonSuggestionFinderService() { MDNSAddonSuggestionFinder mdns = new MDNSAddonSuggestionFinder(mdnsClient); for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { - mdns.addService(service); + mdns.addService(service, true); } for (ServiceInfo service : mdnsClient.list("_printer._tcp.local.")) { - mdns.addService(service); + mdns.addService(service, true); } addonSuggestionFinderService.addAddonSuggestionFinder(mdns); } From a939627a93aafbe94e4976125cd4a7d1a61969ed Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 6 Nov 2023 17:07:43 +0000 Subject: [PATCH 52/98] [addon suggestion finder] move addoninfo changes into second PR Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 8 +- .../schema/addon-1.0.0.xsd | 30 +-- .../core/addon/AddonDiscoveryMethod.java | 81 ------- .../org/openhab/core/addon/AddonInfo.java | 36 +--- .../org/openhab/core/addon/AddonInfoList.java | 38 ---- .../core/addon/AddonInfoListReader.java | 104 --------- .../openhab/core/addon/AddonInfoProvider.java | 6 +- .../openhab/core/addon/AddonInfoRegistry.java | 81 +------ .../core/addon/AddonMatchProperty.java | 73 ------- .../xml/AddonDiscoveryMethodConverter.java | 61 ------ .../internal/xml/AddonInfoConverter.java | 7 - .../internal/xml/AddonInfoListConverter.java | 57 ----- .../addon/internal/xml/AddonInfoReader.java | 13 -- .../xml/AddonMatchPropertyConverter.java | 52 ----- .../addon/test/AddonInfoListReaderTest.java | 121 ----------- .../test/AddonInfoRegistryMergeTest.java | 203 ------------------ .../core/addon/xml/test/AddonInfoTest.java | 33 +-- .../OH-INF/addon/addon.xml | 17 -- 18 files changed, 26 insertions(+), 995 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java delete mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java delete mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index 634118741ac..b5320e6c5b4 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -24,16 +24,16 @@ - + + + - + - - diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index c4ef068ca81..5760d5d1bdf 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -28,8 +28,7 @@ - - + Comma-separated list of two-letter ISO country codes. @@ -96,31 +95,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java deleted file mode 100644 index 88eb8669819..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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.addon; - -import java.util.List; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a suggested addon discovery method. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonDiscoveryMethod { - private @NonNullByDefault({}) String serviceType; - private @Nullable String mdnsServiceType; - private @Nullable List matchProperties; - - public String getServiceType() { - return serviceType.toLowerCase(); - } - - public String getMdnsServiceType() { - String mdnsServiceType = this.mdnsServiceType; - return mdnsServiceType != null ? mdnsServiceType : ""; - } - - public List getMatchProperties() { - List matchProperties = this.matchProperties; - return matchProperties != null ? matchProperties : List.of(); - } - - public AddonDiscoveryMethod setServiceType(String serviceType) { - this.serviceType = serviceType.toLowerCase(); - return this; - } - - public AddonDiscoveryMethod setMdnsServiceType(@Nullable String mdnsServiceType) { - this.mdnsServiceType = mdnsServiceType; - return this; - } - - public AddonDiscoveryMethod setMatchProperties(@Nullable List matchProperties) { - this.matchProperties = matchProperties; - return this; - } - - @Override - public int hashCode() { - return Objects.hash(serviceType, mdnsServiceType, matchProperties); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - AddonDiscoveryMethod other = (AddonDiscoveryMethod) obj; - return Objects.equals(serviceType, other.serviceType) && Objects.equals(mdnsServiceType, other.mdnsServiceType) - && Objects.equals(matchProperties, other.matchProperties); - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index 8e45c47c918..8de5d828f70 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -37,7 +37,6 @@ public class AddonInfo implements Identifiable { private final String id; private final String type; - private final String uid; private final String name; private final String description; private final @Nullable String connection; @@ -45,12 +44,10 @@ public class AddonInfo implements Identifiable { private final @Nullable String configDescriptionURI; private final String serviceId; private @Nullable String sourceBundle; - private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, @Nullable String uid, String name, String description, - @Nullable String connection, List countries, @Nullable String configDescriptionURI, - @Nullable String serviceId, @Nullable String sourceBundle, - @Nullable List discoveryMethods) throws IllegalArgumentException { + private AddonInfo(String id, String type, String name, String description, @Nullable String connection, + List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, + @Nullable String sourceBundle) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -67,7 +64,6 @@ private AddonInfo(String id, String type, @Nullable String uid, String name, Str } this.id = id; this.type = type; - this.uid = uid != null ? uid : type + Addon.ADDON_SEPARATOR + id; this.name = name; this.description = description; @@ -77,7 +73,6 @@ private AddonInfo(String id, String type, @Nullable String uid, String name, Str this.configDescriptionURI = configDescriptionURI; this.serviceId = Objects.requireNonNullElse(serviceId, type + "." + id); this.sourceBundle = sourceBundle; - this.discoveryMethods = discoveryMethods; } /** @@ -87,7 +82,7 @@ private AddonInfo(String id, String type, @Nullable String uid, String name, Str */ @Override public String getUID() { - return uid; + return type + Addon.ADDON_SEPARATOR + id; } /** @@ -147,11 +142,6 @@ public List getCountries() { return countries; } - public List getDiscoveryMethods() { - List discoveryMethods = this.discoveryMethods; - return discoveryMethods != null ? discoveryMethods : List.of(); - } - public static Builder builder(String id, String type) { return new Builder(id, type); } @@ -164,7 +154,6 @@ public static class Builder { private final String id; private final String type; - private @Nullable String uid; private String name = ""; private String description = ""; private @Nullable String connection; @@ -172,7 +161,6 @@ public static class Builder { private @Nullable String configDescriptionURI = ""; private @Nullable String serviceId; private @Nullable String sourceBundle; - private @Nullable List discoveryMethods; private Builder(String id, String type) { this.id = id; @@ -182,7 +170,6 @@ private Builder(String id, String type) { private Builder(AddonInfo addonInfo) { this.id = addonInfo.id; this.type = addonInfo.type; - this.uid = addonInfo.uid; this.name = addonInfo.name; this.description = addonInfo.description; this.connection = addonInfo.connection; @@ -190,12 +177,6 @@ private Builder(AddonInfo addonInfo) { this.configDescriptionURI = addonInfo.configDescriptionURI; this.serviceId = addonInfo.serviceId; this.sourceBundle = addonInfo.sourceBundle; - this.discoveryMethods = addonInfo.discoveryMethods; - } - - public Builder withUID(@Nullable String uid) { - this.uid = uid; - return this; } public Builder withName(String name) { @@ -238,11 +219,6 @@ public Builder withSourceBundle(@Nullable String sourceBundle) { return this; } - public Builder withDiscoveryMethods(@Nullable List discoveryMethods) { - this.discoveryMethods = discoveryMethods; - return this; - } - /** * Build an {@link AddonInfo} from this builder * @@ -250,8 +226,8 @@ public Builder withDiscoveryMethods(@Nullable List discove * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, - serviceId, sourceBundle, discoveryMethods); + return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, + sourceBundle); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java deleted file mode 100644 index 6638d9dec49..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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.addon; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO containing a list of {@code AddonInfo} - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonInfoList { - protected @Nullable List addons; - - public List getAddons() { - List addons = this.addons; - return addons != null ? addons : List.of(); - } - - public AddonInfoList setAddons(@Nullable List addons) { - this.addons = addons; - return this; - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java deleted file mode 100644 index 393444579c1..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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.addon; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.internal.xml.AddonDiscoveryMethodConverter; -import org.openhab.core.addon.internal.xml.AddonInfoConverter; -import org.openhab.core.addon.internal.xml.AddonInfoListConverter; -import org.openhab.core.addon.internal.xml.AddonInfoXmlResult; -import org.openhab.core.addon.internal.xml.AddonMatchPropertyConverter; -import org.openhab.core.config.core.ConfigDescription; -import org.openhab.core.config.core.ConfigDescriptionParameter; -import org.openhab.core.config.core.ConfigDescriptionParameterGroup; -import org.openhab.core.config.core.FilterCriteria; -import org.openhab.core.config.core.xml.ConfigDescriptionConverter; -import org.openhab.core.config.core.xml.ConfigDescriptionParameterConverter; -import org.openhab.core.config.core.xml.ConfigDescriptionParameterGroupConverter; -import org.openhab.core.config.core.xml.FilterCriteriaConverter; -import org.openhab.core.config.core.xml.util.NodeAttributes; -import org.openhab.core.config.core.xml.util.NodeAttributesConverter; -import org.openhab.core.config.core.xml.util.NodeList; -import org.openhab.core.config.core.xml.util.NodeListConverter; -import org.openhab.core.config.core.xml.util.NodeValue; -import org.openhab.core.config.core.xml.util.NodeValueConverter; -import org.openhab.core.config.core.xml.util.XmlDocumentReader; - -import com.thoughtworks.xstream.XStream; - -/** - * The {@link AddonInfoListReader} reads XML documents, which contain the {@code binding} XML tag, and converts them to - * a List of {@link AddonInfoXmlResult} objects. - *

- * This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonInfoListReader extends XmlDocumentReader { - - /** - * The default constructor of this class. - */ - public AddonInfoListReader() { - ClassLoader classLoader = AddonInfoListReader.class.getClassLoader(); - if (classLoader != null) { - super.setClassLoader(classLoader); - } - } - - @Override - protected void registerConverters(XStream xstream) { - xstream.registerConverter(new NodeAttributesConverter()); - xstream.registerConverter(new NodeListConverter()); - xstream.registerConverter(new NodeValueConverter()); - xstream.registerConverter(new AddonInfoListConverter()); - xstream.registerConverter(new AddonInfoConverter()); - xstream.registerConverter(new ConfigDescriptionConverter()); - xstream.registerConverter(new ConfigDescriptionParameterConverter()); - xstream.registerConverter(new ConfigDescriptionParameterGroupConverter()); - xstream.registerConverter(new FilterCriteriaConverter()); - xstream.registerConverter(new AddonDiscoveryMethodConverter()); - xstream.registerConverter(new AddonMatchPropertyConverter()); - } - - @Override - protected void registerAliases(XStream xstream) { - xstream.alias("addon-info-list", AddonInfoList.class); - xstream.alias("addons", NodeList.class); - xstream.alias("addon", AddonInfoXmlResult.class); - xstream.alias("name", NodeValue.class); - xstream.alias("description", NodeValue.class); - xstream.alias("type", NodeValue.class); - xstream.alias("connection", NodeValue.class); - xstream.alias("countries", NodeValue.class); - xstream.alias("config-description", ConfigDescription.class); - xstream.alias("config-description-ref", NodeAttributes.class); - xstream.alias("parameter", ConfigDescriptionParameter.class); - xstream.alias("parameter-group", ConfigDescriptionParameterGroup.class); - xstream.alias("options", NodeList.class); - xstream.alias("option", NodeValue.class); - xstream.alias("filter", List.class); - xstream.alias("criteria", FilterCriteria.class); - xstream.alias("service-id", NodeValue.class); - xstream.alias("discovery-methods", NodeList.class); - xstream.alias("discovery-method", AddonDiscoveryMethod.class); - xstream.alias("service-type", NodeValue.class); - xstream.alias("mdns-service-type", NodeValue.class); - xstream.alias("match-properties", NodeList.class); - xstream.alias("match-property", AddonMatchProperty.class); - xstream.alias("regex", NodeValue.class); - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java index b444451afb4..edd8b095883 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java @@ -31,15 +31,15 @@ public interface AddonInfoProvider { /** - * Returns the binding information for the specified binding UID and locale (language), + * Returns the binding information for the specified binding ID and locale (language), * or {@code null} if no binding information could be found. * - * @param uid the UID to be looked for (could be null or empty) + * @param id the ID to be looked for (could be null or empty) * @param locale the locale to be used for the binding information (could be null) * @return a localized binding information object (could be null) */ @Nullable - AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale); + AddonInfo getAddonInfo(@Nullable String id, @Nullable Locale locale); /** * Returns all binding information in the specified locale (language) this provider contains. diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 7b4c1e21df0..678c329ca2d 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -13,13 +13,10 @@ package org.openhab.core.addon; import java.util.Collection; -import java.util.HashSet; import java.util.Locale; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.BinaryOperator; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -43,94 +40,38 @@ public class AddonInfoRegistry { private final Collection addonInfoProviders = new CopyOnWriteArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + protected void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); } - public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + protected void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.remove(addonInfoProvider); } /** - * Returns the add-on information for the specified add-on UID, or {@code null} if no add-on information could be + * Returns the add-on information for the specified add-on ID, or {@code null} if no add-on information could be * found. * - * @param uid the UID to be looked + * @param id the ID to be looked * @return a add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String uid) { - return getAddonInfo(uid, null); + public @Nullable AddonInfo getAddonInfo(String id) { + return getAddonInfo(id, null); } /** - * Returns the add-on information for the specified add-on UID and locale (language), + * Returns the add-on information for the specified add-on ID and locale (language), * or {@code null} if no add-on information could be found. - *

- * If more than one provider provides information for the specified add-on UID and locale, - * it returns a new {@link AddonInfo} containing merged information from all such providers. * - * @param uid the UID to be looked for + * @param id the ID to be looked for * @param locale the locale to be used for the add-on information (could be null) * @return a localized add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) { - return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)).filter(Objects::nonNull) - .collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(), - Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get))) - .get(uid); + public @Nullable AddonInfo getAddonInfo(String id, @Nullable Locale locale) { + return addonInfoProviders.stream().map(p -> p.getAddonInfo(id, locale)).filter(Objects::nonNull).findAny() + .orElse(null); } - /** - * A {@link BinaryOperator} to merge the field values from two {@link AddonInfo} objects into a third such object. - *

- * If the first object has a non-null field value the result object takes the first value, or if the second object - * has a non-null field value the result object takes the second value. Otherwise the field remains null. - * - * @param a the first {@link AddonInfo} (could be null) - * @param b the second {@link AddonInfo} (could be null) - * @return a new {@link AddonInfo} containing the combined field values (could be null) - */ - private static BinaryOperator<@Nullable AddonInfo> mergeAddonInfos = (a, b) -> { - if (a == null) { - return b; - } else if (b == null) { - return a; - } - AddonInfo.Builder builder = AddonInfo.builder(a); - if (a.getDescription().isEmpty()) { - builder.withDescription(b.getDescription()); - } - if (a.getConnection() == null && b.getConnection() != null) { - builder.withConnection(b.getConnection()); - } - Set countries = new HashSet<>(a.getCountries()); - countries.addAll(b.getCountries()); - if (!countries.isEmpty()) { - builder.withCountries(countries.stream().toList()); - } - String aConfigDescriptionURI = a.getConfigDescriptionURI(); - if (aConfigDescriptionURI == null || aConfigDescriptionURI.isEmpty() && b.getConfigDescriptionURI() != null) { - builder.withConfigDescriptionURI(b.getConfigDescriptionURI()); - } - if (a.getSourceBundle() == null && b.getSourceBundle() != null) { - builder.withSourceBundle(b.getSourceBundle()); - } - String defaultServiceId = a.getType() + "." + a.getId(); - if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) { - builder.withServiceId(b.getServiceId()); - } - String defaultUID = a.getType() + Addon.ADDON_SEPARATOR + a.getId(); - if (defaultUID.equals(a.getUID()) && !defaultUID.equals(b.getUID())) { - builder.withUID(b.getUID()); - } - Set discoveryMethods = new HashSet<>(a.getDiscoveryMethods()); - discoveryMethods.addAll(b.getDiscoveryMethods()); - if (!discoveryMethods.isEmpty()) { - builder.withDiscoveryMethods(discoveryMethods.stream().toList()); - } - return builder.build(); - }; - /** * Returns all add-on information this registry contains. * diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java deleted file mode 100644 index ac5ccebe9b3..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 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.addon; - -import java.util.Objects; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * DTO for serialization of a property match regular expression. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonMatchProperty { - private @NonNullByDefault({}) String name; - private @NonNullByDefault({}) String regex; - private transient @NonNullByDefault({}) Pattern pattern; - - public AddonMatchProperty(String name, String regex) { - this.name = name; - this.regex = regex; - this.pattern = null; - } - - public String getName() { - return name; - } - - public Pattern getPattern() { - Pattern pattern = this.pattern; - if (pattern == null) { - this.pattern = Pattern.compile(regex); - } - return this.pattern; - } - - public String getRegex() { - return regex; - } - - @Override - public int hashCode() { - return Objects.hash(name, regex); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - AddonMatchProperty other = (AddonMatchProperty) obj; - return Objects.equals(name, other.name) && Objects.equals(regex, other.regex); - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java deleted file mode 100644 index 39110721aec..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 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.addon.internal.xml; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonMatchProperty; -import org.openhab.core.config.core.xml.util.GenericUnmarshaller; -import org.openhab.core.config.core.xml.util.NodeIterator; - -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; - -/** - * The {@link AddonDiscoveryMethodConverter} is a concrete implementation of the {@code XStream} {@link Converter} - * interface used to convert add-on discovery method information within an XML document into a - * {@link AddonDiscoveryMethod} object. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonDiscoveryMethodConverter extends GenericUnmarshaller { - - public AddonDiscoveryMethodConverter() { - super(AddonDiscoveryMethod.class); - } - - @Override - public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - List nodes = (List) context.convertAnother(context, List.class); - NodeIterator nodeIterator = new NodeIterator(nodes); - - String serviceType = requireNonEmpty((String) nodeIterator.nextValue("service-type", true), - "Service type is null or empty"); - - String mdnsServiceType = (String) nodeIterator.nextValue("mdns-service-type", false); - - Object object = nodeIterator.nextList("match-properties", false); - List matchProperties = !(object instanceof List list) ? null - : list.stream().filter(e -> (e instanceof AddonMatchProperty)).map(e -> ((AddonMatchProperty) e)) - .toList(); - - nodeIterator.assertEndOfType(); - - return new AddonDiscoveryMethod().setServiceType(serviceType).setMdnsServiceType(mdnsServiceType) - .setMatchProperties(matchProperties); - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java index 2e1db84409a..257dfff682f 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java @@ -18,7 +18,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionBuilder; @@ -38,7 +37,6 @@ * @author Michael Grammling - Initial contribution * @author Andre Fuechsel - Made author tag optional * @author Jan N. Klug - Refactored to cover all add-ons - * @author Andrew Fiddian-Green - Added discovery methods */ @NonNullByDefault public class AddonInfoConverter extends GenericUnmarshaller { @@ -109,11 +107,6 @@ public AddonInfoConverter() { addonInfo.withConfigDescriptionURI(configDescriptionURI); - Object object = nodeIterator.nextList("discovery-methods", false); - addonInfo.withDiscoveryMethods(!(object instanceof List list) ? null - : list.stream().filter(e -> (e instanceof AddonDiscoveryMethod)).map(e -> ((AddonDiscoveryMethod) e)) - .toList()); - nodeIterator.assertEndOfType(); // create object diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java deleted file mode 100644 index 60a93ad0027..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.addon.internal.xml; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.config.core.xml.util.GenericUnmarshaller; -import org.openhab.core.config.core.xml.util.NodeIterator; - -import com.thoughtworks.xstream.converters.Converter; -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; - -/** - * The {@link AddonInfoListConverter} is a concrete implementation of the {@code XStream} {@link Converter} - * interface used to convert a list of add-on information within an XML document into a list of {@link AddonInfo} - * objects. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonInfoListConverter extends GenericUnmarshaller { - - public AddonInfoListConverter() { - super(AddonInfoList.class); - } - - @Override - public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - List nodes = (List) context.convertAnother(context, List.class); - NodeIterator nodeIterator = new NodeIterator(nodes); - - Object object = nodeIterator.nextList("addons", false); - List addons = (object instanceof List list) - ? list.stream().filter(e -> e != null).filter(e -> (e instanceof AddonInfoXmlResult)) - .map(e -> (AddonInfoXmlResult) e).map(r -> r.addonInfo()).toList() - : null; - - nodeIterator.assertEndOfType(); - - return new AddonInfoList().setAddons(addons); - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java index 47cad85e943..88309b3eb59 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java @@ -15,8 +15,6 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; @@ -28,7 +26,6 @@ import org.openhab.core.config.core.xml.util.NodeAttributes; import org.openhab.core.config.core.xml.util.NodeAttributesConverter; import org.openhab.core.config.core.xml.util.NodeList; -import org.openhab.core.config.core.xml.util.NodeListConverter; import org.openhab.core.config.core.xml.util.NodeValue; import org.openhab.core.config.core.xml.util.NodeValueConverter; import org.openhab.core.config.core.xml.util.XmlDocumentReader; @@ -62,15 +59,12 @@ public AddonInfoReader() { @Override protected void registerConverters(XStream xstream) { xstream.registerConverter(new NodeAttributesConverter()); - xstream.registerConverter(new NodeListConverter()); xstream.registerConverter(new NodeValueConverter()); xstream.registerConverter(new AddonInfoConverter()); xstream.registerConverter(new ConfigDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionParameterConverter()); xstream.registerConverter(new ConfigDescriptionParameterGroupConverter()); xstream.registerConverter(new FilterCriteriaConverter()); - xstream.registerConverter(new AddonDiscoveryMethodConverter()); - xstream.registerConverter(new AddonMatchPropertyConverter()); } @Override @@ -90,12 +84,5 @@ protected void registerAliases(XStream xstream) { xstream.alias("filter", List.class); xstream.alias("criteria", FilterCriteria.class); xstream.alias("service-id", NodeValue.class); - xstream.alias("discovery-methods", NodeList.class); - xstream.alias("discovery-method", AddonDiscoveryMethod.class); - xstream.alias("service-type", NodeValue.class); - xstream.alias("mdns-service-type", NodeValue.class); - xstream.alias("match-properties", NodeList.class); - xstream.alias("match-property", AddonMatchProperty.class); - xstream.alias("regex", NodeValue.class); } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java deleted file mode 100644 index f8f5321d9de..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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.addon.internal.xml; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonMatchProperty; -import org.openhab.core.config.core.xml.util.GenericUnmarshaller; -import org.openhab.core.config.core.xml.util.NodeIterator; - -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; - -/** - * The {@link AddonMatchPropertyConverter} is a concrete implementation of the {@code XStream} {@link Converter} - * interface used to convert add-on discovery method match property information within an XML document into a - * {@link AddonMatchProperty} object. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public class AddonMatchPropertyConverter extends GenericUnmarshaller { - - public AddonMatchPropertyConverter() { - super(AddonMatchProperty.class); - } - - @Override - public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - List nodes = (List) context.convertAnother(context, List.class); - NodeIterator nodeIterator = new NodeIterator(nodes); - - String name = requireNonEmpty((String) nodeIterator.nextValue("name", true), "Name is null or empty"); - String regex = requireNonEmpty((String) nodeIterator.nextValue("regex", true), "Regex is null or empty"); - - nodeIterator.assertEndOfType(); - - return new AddonMatchProperty(name, regex); - } -} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java deleted file mode 100644 index bc20aaa6620..00000000000 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 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.addon.test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Test; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.addon.AddonInfoListReader; -import org.openhab.core.addon.AddonMatchProperty; - -/** - * JUnit tests for {@link AddonInfoListReader}. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -class AddonInfoListReaderTest { - - // @formatter:off - private final String testXml = - "" - + " " - + " automation" - + " Groovy Scripting" - + " This adds a Groovy script engine." - + " none" - + " " - + " " - + " mdns" - + " _printer._tcp.local." - + " " - + " " - + " rp" - + " .*" - + " " - + " " - + " ty" - + " hp (.*)" - + " " - + " " - + " " - + " " - + " upnp" - + " " - + " " - + " modelName" - + " Philips hue bridge" - + " " - + " " - + " " - + " " - + " " - + ""; - // @formatter:on - - @Test - void testAddonInfoListReader() { - AddonInfoList addons = null; - try { - AddonInfoListReader reader = new AddonInfoListReader(); - addons = reader.readFromXML(testXml); - } catch (Exception e) { - fail(e); - } - assertNotNull(addons); - List addonsInfos = addons.getAddons(); - assertEquals(1, addonsInfos.size()); - AddonInfo addon = addonsInfos.get(0); - assertNotNull(addon); - List discoveryMethods = addon.getDiscoveryMethods(); - assertNotNull(discoveryMethods); - assertEquals(2, discoveryMethods.size()); - - AddonDiscoveryMethod method = discoveryMethods.get(0); - assertNotNull(method); - assertEquals("mdns", method.getServiceType()); - assertEquals("_printer._tcp.local.", method.getMdnsServiceType()); - List matchProperties = method.getMatchProperties(); - assertNotNull(matchProperties); - assertEquals(2, matchProperties.size()); - AddonMatchProperty property = matchProperties.get(0); - assertNotNull(property); - assertEquals("rp", property.getName()); - assertEquals(".*", property.getRegex()); - assertTrue(property.getPattern().matcher("the cat sat on the mat").matches()); - - method = discoveryMethods.get(1); - assertNotNull(method); - assertEquals("upnp", method.getServiceType()); - assertEquals("", method.getMdnsServiceType()); - matchProperties = method.getMatchProperties(); - assertNotNull(matchProperties); - assertEquals(1, matchProperties.size()); - property = matchProperties.get(0); - assertNotNull(property); - assertEquals("modelName", property.getName()); - assertEquals("Philips hue bridge", property.getRegex()); - assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); - } -} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java deleted file mode 100644 index e7cb73cd6dd..00000000000 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * 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.addon.test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonInfoRegistry; -import org.openhab.core.addon.AddonMatchProperty; - -/** - * JUnit test for the {@link AddonInfoRegistry} merge function. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@TestInstance(Lifecycle.PER_CLASS) -class AddonInfoRegistryMergeTest { - - private @Nullable AddonInfoProvider addonInfoProvider0; - private @Nullable AddonInfoProvider addonInfoProvider1; - private @Nullable AddonInfoProvider addonInfoProvider2; - - @BeforeAll - void beforeAll() { - addonInfoProvider0 = createAddonInfoProvider0(); - addonInfoProvider1 = createAddonInfoProvider1(); - addonInfoProvider2 = createAddonInfoProvider2(); - } - - private AddonInfoProvider createAddonInfoProvider0() { - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero") - .withDescription("description-zero").build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - private AddonInfoProvider createAddonInfoProvider1() { - AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns") - .setMdnsServiceType("_hue._tcp.local."); - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one") - .withDescription("description-one").withCountries("GB,NL").withConnection("local") - .withDiscoveryMethods(List.of(discoveryMethod)).build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - private AddonInfoProvider createAddonInfoProvider2() { - AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("upnp") - .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") - .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") - .withServiceId("service-id").withConfigDescriptionURI("http://www.openhab.org") - .withDiscoveryMethods(List.of(discoveryMethod)).build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - /** - * Test fetching a single addon-info from the registry with no merging. - */ - @Test - void testGetOneAddonInfo() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertNull(addonInfo.getSourceBundle()); - assertNotEquals("local", addonInfo.getConnection()); - assertEquals(0, addonInfo.getCountries().size()); - assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("binding.hue", addonInfo.getServiceId()); - assertEquals(0, addonInfo.getDiscoveryMethods().size()); - } - - /** - * Test fetching two addon-info's from the registry with merging. - */ - @Test - void testMergeAddonInfos2() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - assertNotNull(addonInfoProvider1); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertNull(addonInfo.getSourceBundle()); - assertEquals("local", addonInfo.getConnection()); - assertEquals(2, addonInfo.getCountries().size()); - assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("binding.hue", addonInfo.getServiceId()); - assertEquals(1, addonInfo.getDiscoveryMethods().size()); - } - - /** - * Test fetching three addon-info's from the registry with full merging. - */ - @Test - void testMergeAddonInfos3() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - assertNotNull(addonInfoProvider1); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); - assertNotNull(addonInfoProvider2); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider2)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertEquals("source-bundle", addonInfo.getSourceBundle()); - assertEquals("local", addonInfo.getConnection()); - assertEquals(4, addonInfo.getCountries().size()); - assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("service-id", addonInfo.getServiceId()); - assertEquals(2, addonInfo.getDiscoveryMethods().size()); - } -} diff --git a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java index 3e109b7a5a9..1531c2ab4b8 100644 --- a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java +++ b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java @@ -12,12 +12,8 @@ */ package org.openhab.core.addon.xml.test; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.List; @@ -28,10 +24,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoRegistry; -import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionRegistry; @@ -72,31 +66,6 @@ public void assertThatAddonInfoIsReadProperly() throws Exception { assertThat(addonInfo.getDescription(), is("The hue Binding integrates the Philips hue system. It allows to control hue lights.")); assertThat(addonInfo.getName(), is("hue Binding")); - - List discoveryMethods = addonInfo.getDiscoveryMethods(); - assertNotNull(discoveryMethods); - assertEquals(2, discoveryMethods.size()); - - AddonDiscoveryMethod discoveryMethod = discoveryMethods.get(0); - assertNotNull(discoveryMethod); - assertEquals("mdns", discoveryMethod.getServiceType()); - assertEquals("_hue._tcp.local.", discoveryMethod.getMdnsServiceType()); - List properties = discoveryMethod.getMatchProperties(); - assertNotNull(properties); - assertEquals(0, properties.size()); - - discoveryMethod = discoveryMethods.get(1); - assertNotNull(discoveryMethod); - assertEquals("upnp", discoveryMethod.getServiceType()); - assertEquals("", discoveryMethod.getMdnsServiceType()); - properties = discoveryMethod.getMatchProperties(); - assertNotNull(properties); - assertEquals(1, properties.size()); - AddonMatchProperty property = properties.get(0); - assertNotNull(property); - assertEquals("modelName", property.getName()); - assertEquals("Philips hue bridge", property.getRegex()); - assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); }); } diff --git a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml index 9e5db0944bd..4d351403227 100644 --- a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml +++ b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml @@ -30,21 +30,4 @@ - - - - mdns - _hue._tcp.local. - - - upnp - - - modelName - Philips hue bridge - - - - - From 56a3a5b5a22c6f5bfb23bdc79639a435a33fae0e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 6 Nov 2023 17:18:20 +0000 Subject: [PATCH 53/98] [addon suggestion finder] align PR contents Signed-off-by: Andrew Fiddian-Green --- .../eclipse/internal/EclipseAddonService.java | 1 + .../config/core/xml/util/XmlDocumentReader.java | 17 ----------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java index e975396ef7d..6e5f063850a 100644 --- a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java +++ b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java @@ -142,6 +142,7 @@ private Addon getAddon(Bundle bundle, @Nullable Locale locale) { AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale); if (addonInfo != null) { + // only enrich if this add-on is installed, otherwise wrong data might be added addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription()) .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withLink(getDefaultDocumentationLink(type, name)) diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java index a413fcd9930..caa8bfd343a 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java @@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.Nullable; import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.io.xml.StaxDriver; @@ -105,20 +104,4 @@ protected void configureSecurity(XStream xstream) { public @Nullable T readFromXML(URL xmlURL) throws ConversionException { return (@Nullable T) xstream.fromXML(xmlURL); } - - /** - * Reads the XML document containing a specific XML tag from the specified xml string and converts it to the - * according object. - *

- * This method returns {@code null} if the given URL is {@code null}. - * - * @param xml a string containing the XML document to be read. - * @return the conversion result object (could be null). - * @throws XStreamException if the object cannot be deserialized. - * @throws ConversionException if the specified document contains invalid content - */ - @SuppressWarnings("unchecked") - public @Nullable T readFromXML(String xml) throws ConversionException { - return (@Nullable T) xstream.fromXML(xml); - } } From 968ec3aebdc09e0e411692575702981294e33456 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 7 Nov 2023 17:48:01 +0000 Subject: [PATCH 54/98] [addon suggestion finder] add means to disable finders Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionFinderService.java | 11 ++++ .../addon/finders/AddonSuggestionFinder.java | 25 ++++++++- .../finders/BaseAddonSuggestionFinder.java | 14 ++--- .../finders/MDNSAddonSuggestionFinder.java | 52 +++++++++++++------ .../finders/UpnpAddonSuggestionFinder.java | 39 +++++++++++--- 5 files changed, 107 insertions(+), 34 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 24e5a23d537..47c269f6aee 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -87,6 +87,17 @@ public void close() throws Exception { addonInfoProviders.clear(); } + /** + * The OH framework calls this method to enable/disable the finder with the given service id (if any). + * + * @param finderServiceId the service id of the finder to be enabled/disabled. + * @param enable true to enable, false to disable + */ + public void enable(String finderServiceId, boolean enable) { + addonSuggestionFinders.stream().filter(f -> finderServiceId.equals(f.getServiceType())).findFirst() + .ifPresent(f -> f.enable(enable)); + } + public Set getSuggestedAddons(@Nullable Locale locale) { return addonSuggestionFinders.stream().map(f -> f.getSuggestedAddons()).flatMap(Collection::stream) .collect(Collectors.toSet()); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java index 2e8809ce3e7..0f8c5afca94 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -27,13 +27,34 @@ public interface AddonSuggestionFinder { /** - * The framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of + * This method should be overridden and referenced so that the OSGI framework will call it when the component is + * deactivated. It should clear the finder's internal state. + */ + public void deactivate(); + + /** + * The OH framework calls this method to enable or disable the finder. Typically e.g. for upnp and mdns, it connects + * to resp. disconnects from, the underlying discovery transport. + * + * @param enable true to enable, false to disable + */ + public void enable(boolean enable); + + /** + * The OH framework calls this method to read the finder service type. + * + * @return the finder service type + */ + public String getServiceType(); + + /** + * The OH framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of * those that it suggests to be installed. */ public Set getSuggestedAddons(); /** - * The framework calls this method to provide a list of {@link AddonInfo} elements which contain potential + * The OH framework calls this method to provide a list of {@link AddonInfo} elements which contain potential * candidates that this finder can iterate over in order to detect which ones to return via the * {@code getSuggestedAddons()} method. * diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index b17d459321d..8e3f50c7723 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -16,7 +16,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -51,18 +50,13 @@ protected static boolean propertyMatches(Map propertyPatternMap protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - protected void deactivate() { - resetAddonCandidates(); - } - - public abstract Set getSuggestedAddons(); - - protected void resetAddonCandidates() { + @Override + public void deactivate() { addonCandidates.clear(); } - public void setAddonCandidates(List candidates) { - resetAddonCandidates(); + public synchronized void setAddonCandidates(List candidates) { + addonCandidates.clear(); addonCandidates.addAll(candidates); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 6905084c604..21bf4c21176 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -71,13 +71,43 @@ public void addService(ServiceInfo service, boolean isResolved) { } } + private void connect() { + addonCandidates + .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) + .filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> { + String serviceType = m.getMdnsServiceType(); + mdnsClient.addServiceListener(serviceType, this); + scheduler.submit(() -> mdnsClient.list(serviceType)); + })); + } + @Deactivate @Override - protected void deactivate() { + public void deactivate() { services.clear(); + disconnect(); super.deactivate(); } + private void disconnect() { + addonCandidates.forEach(c -> c.getDiscoveryMethods().stream() + .filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty()) + .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); + } + + @Override + public void enable(boolean enable) { + disconnect(); + if (enable) { + connect(); + } + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); @@ -110,6 +140,10 @@ && propertyMatches(matchProperties, APPLICATION, service.getApplication()) return result; } + /* + * ************ MDNSClient call-back methods ************ + */ + @Override public void serviceAdded(@Nullable ServiceEvent event) { if (event != null) { @@ -134,22 +168,10 @@ public void serviceResolved(@Nullable ServiceEvent event) { } } - @Override - protected void resetAddonCandidates() { - addonCandidates.forEach(c -> c.getDiscoveryMethods().stream() - .filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty()) - .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); - super.resetAddonCandidates(); - } - @Override public void setAddonCandidates(List candidates) { + disconnect(); super.setAddonCandidates(candidates); - candidates.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) - .filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> { - String serviceType = m.getMdnsServiceType(); - mdnsClient.addServiceListener(serviceType, this); - scheduler.submit(() -> mdnsClient.list(serviceType)); - })); + connect(); } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index fe9c7c712e1..a38885a695e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -74,11 +74,7 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme @Activate public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; - Registry registry = upnpService.getRegistry(); - for (RemoteDevice device : registry.getRemoteDevices()) { - remoteDeviceAdded(registry, device); - } - registry.addListener(this); + connect(); } public void addDevice(RemoteDevice device) { @@ -93,14 +89,39 @@ public void addDevice(RemoteDevice device) { } } + private void connect() { + Registry registry = upnpService.getRegistry(); + for (RemoteDevice device : registry.getRemoteDevices()) { + remoteDeviceAdded(registry, device); + } + registry.addListener(this); + } + @Deactivate @Override - protected void deactivate() { + public void deactivate() { devices.clear(); - upnpService.getRegistry().removeListener(this); + disconnect(); super.deactivate(); } + private void disconnect() { + upnpService.getRegistry().removeListener(this); + } + + @Override + public void enable(boolean enable) { + disconnect(); + if (enable) { + connect(); + } + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); @@ -179,6 +200,10 @@ && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { return result; } + /* + * ************ UpnpService call-back methods ************ + */ + @Override public void afterShutdown() { } From 5716c842e61451dab95b2bf214417e57d249dfab Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 17:33:50 +0000 Subject: [PATCH 55/98] [addon suggestion finder] fix osgi timing issue Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionFinderService.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 47c269f6aee..33eb506f0f8 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -57,27 +57,31 @@ public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); - addonInfoProvidersChanged(); + changed(); } public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - addonInfoProviders.remove(addonInfoProvider); - addonInfoProvidersChanged(); - } - - private void addonInfoProvidersChanged() { - List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) - .flatMap(Collection::stream).toList(); - addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); + if (addonInfoProviders.remove(addonInfoProvider)) { + changed(); + } } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { addonSuggestionFinders.add(addonSuggestionFinder); + changed(); } public void removeAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { - addonSuggestionFinders.remove(addonSuggestionFinder); + if (addonSuggestionFinders.remove(addonSuggestionFinder)) { + changed(); + } + } + + private void changed() { + List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) + .flatMap(Collection::stream).toList(); + addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); } @Deactivate From 7cda06145bc66734d83f4560cd20527d6a8e6fa5 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 11 Nov 2023 13:50:07 +0000 Subject: [PATCH 56/98] [addon suggestion finder] add configuration to OSGI methods Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionFinderService.java | 11 --- .../addon/finders/AddonSuggestionFinder.java | 21 ------ .../finders/BaseAddonSuggestionFinder.java | 73 ++++++++++++++++++- .../finders/MDNSAddonSuggestionFinder.java | 42 ++++++----- .../finders/UpnpAddonSuggestionFinder.java | 44 ++++++----- .../AddonSuggestionFinderServiceTests.java | 4 +- 6 files changed, 121 insertions(+), 74 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java index 33eb506f0f8..975b3a3d2a6 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java @@ -91,17 +91,6 @@ public void close() throws Exception { addonInfoProviders.clear(); } - /** - * The OH framework calls this method to enable/disable the finder with the given service id (if any). - * - * @param finderServiceId the service id of the finder to be enabled/disabled. - * @param enable true to enable, false to disable - */ - public void enable(String finderServiceId, boolean enable) { - addonSuggestionFinders.stream().filter(f -> finderServiceId.equals(f.getServiceType())).findFirst() - .ifPresent(f -> f.enable(enable)); - } - public Set getSuggestedAddons(@Nullable Locale locale) { return addonSuggestionFinders.stream().map(f -> f.getSuggestedAddons()).flatMap(Collection::stream) .collect(Collectors.toSet()); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java index 0f8c5afca94..3cb727b53c6 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java @@ -26,27 +26,6 @@ @NonNullByDefault public interface AddonSuggestionFinder { - /** - * This method should be overridden and referenced so that the OSGI framework will call it when the component is - * deactivated. It should clear the finder's internal state. - */ - public void deactivate(); - - /** - * The OH framework calls this method to enable or disable the finder. Typically e.g. for upnp and mdns, it connects - * to resp. disconnects from, the underlying discovery transport. - * - * @param enable true to enable, false to disable - */ - public void enable(boolean enable); - - /** - * The OH framework calls this method to read the finder service type. - * - * @return the finder service type - */ - public String getServiceType(); - /** * The OH framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of * those that it suggests to be installed. diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index 8e3f50c7723..4121c6f8e0e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -30,7 +30,9 @@ @NonNullByDefault public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder { + public static final String ADDON_SUGGESTION_FINDER_ENABLED_PROPERTY = "enabled"; protected static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; + protected static final String ADDON_SUGGESTION_FINDER_CONFIG_PID = "discovery.addon."; /** * Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the @@ -49,12 +51,81 @@ protected static boolean propertyMatches(Map propertyPatternMap } protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); + private boolean connected = false; - @Override + /** + * Implementation classes must implement their specific constructor and OSGI annotate it so that the OSGI framework + * will call it when the component is activated. Such methods must call this {@code activate()} method. Depending on + * the given configuration properties, it either connects (enables) or disconnects (disables) the service. + * + * @param configProperties the configuration properties. + */ + protected void activate(@Nullable Map configProperties) { + if (connected ^ getTargetConnected(configProperties)) { + if (connected) { + disconnect(); + } else + connect(); + } + } + + /** + * Implementation classes must override this method in order to connect (enable) the service. Implementations must + * call {@code super.connect()}. + */ + protected void connect() { + connected = true; + } + + /** + * Implementation classes must override this method and OSGI annotate it so that the OSGI framework will call it + * when the component is deactivated. Overridden methods must call {@code super.deactivate()}. Generally it should + * clear the finder state and disable the service. + */ public void deactivate() { + if (connected) { + disconnect(); + } addonCandidates.clear(); } + /** + * Implementation classes must override this method in order to disconnect (disable) the service. Implementations + * must call {@code super.disconnect()}. + */ + protected void disconnect() { + connected = false; + } + + /** + * Helper method that reads a configuration property and determines the target connected state. + * + * @param configProperties the configuration properties. + * @return true if the target is for the service to be connected, false otherwise. + */ + private boolean getTargetConnected(@Nullable Map configProperties) { + if (configProperties != null) { + Object property = configProperties.get(ADDON_SUGGESTION_FINDER_ENABLED_PROPERTY); + if (property instanceof String string) { + return Boolean.valueOf(string); + } else { + return !Boolean.FALSE.equals(property); + } + } + return true; + } + + /** + * Implementation classes must override this method and OSGI annotate it so that the OSGI framework will call it + * when the component configuration is modified. Overridden methods must call {@code super.modified()}. Generally + * it should have the same effect as the {@code activate()} method. + * + * @param configProperties the modified configuration properties. + */ + public void modified(@Nullable Map configProperties) { + activate(configProperties); + } + public synchronized void setAddonCandidates(List candidates) { addonCandidates.clear(); addonCandidates.addAll(candidates); diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 21bf4c21176..73b2a17b1b9 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -34,6 +34,7 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,11 +45,12 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME) +@Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME, configurationPid = MDNSAddonSuggestionFinder.CONFIG_PID) public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder implements ServiceListener { public static final String SERVICE_TYPE = "mdns"; public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; + public static final String CONFIG_PID = ADDON_SUGGESTION_FINDER_CONFIG_PID + SERVICE_TYPE; private static final String NAME = "name"; private static final String APPLICATION = "application"; @@ -59,10 +61,16 @@ public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private final MDNSClient mdnsClient; @Activate - public MDNSAddonSuggestionFinder(@Reference MDNSClient mdnsClient) { + public MDNSAddonSuggestionFinder(@Nullable Map configProperties, @Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; + activate(configProperties); } + /** + * Adds the given mDNS service to the set of discovered services. + * + * @param device the mDNS service to be added. + */ public void addService(ServiceInfo service, boolean isResolved) { String qualifiedName = service.getQualifiedName(); if (isResolved || !services.containsKey(qualifiedName)) { @@ -71,7 +79,8 @@ public void addService(ServiceInfo service, boolean isResolved) { } } - private void connect() { + @Override + protected void connect() { addonCandidates .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) .filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> { @@ -79,33 +88,22 @@ private void connect() { mdnsClient.addServiceListener(serviceType, this); scheduler.submit(() -> mdnsClient.list(serviceType)); })); + super.connect(); } @Deactivate @Override public void deactivate() { - services.clear(); - disconnect(); super.deactivate(); + services.clear(); } - private void disconnect() { + @Override + protected void disconnect() { addonCandidates.forEach(c -> c.getDiscoveryMethods().stream() .filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty()) .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); - } - - @Override - public void enable(boolean enable) { - disconnect(); - if (enable) { - connect(); - } - } - - @Override - public String getServiceType() { - return SERVICE_TYPE; + super.disconnect(); } @Override @@ -140,6 +138,12 @@ && propertyMatches(matchProperties, APPLICATION, service.getApplication()) return result; } + @Modified + @Override + public void modified(@Nullable Map configProperties) { + super.modified(configProperties); + } + /* * ************ MDNSClient call-back methods ************ */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index a38885a695e..6e82561fd8b 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -38,6 +38,7 @@ import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,11 +49,12 @@ * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME) +@Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME, configurationPid = UpnpAddonSuggestionFinder.CONFIG_PID) public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder implements RegistryListener { public static final String SERVICE_TYPE = "upnp"; public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; + public static final String CONFIG_PID = ADDON_SUGGESTION_FINDER_CONFIG_PID + SERVICE_TYPE; private static final String DEVICE_TYPE = "deviceType"; private static final String MANUFACTURER = "manufacturer"; @@ -72,11 +74,17 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private final UpnpService upnpService; @Activate - public UpnpAddonSuggestionFinder(@Reference UpnpService upnpService) { + public UpnpAddonSuggestionFinder(@Nullable Map configProperties, + @Reference UpnpService upnpService) { this.upnpService = upnpService; - connect(); + activate(configProperties); } + /** + * Adds the given UPnP remote device to the set of discovered devices. + * + * @param device the UPnP remote device to be added. + */ public void addDevice(RemoteDevice device) { RemoteDeviceIdentity identity = device.getIdentity(); if (identity != null) { @@ -89,37 +97,27 @@ public void addDevice(RemoteDevice device) { } } - private void connect() { + @Override + protected void connect() { Registry registry = upnpService.getRegistry(); for (RemoteDevice device : registry.getRemoteDevices()) { remoteDeviceAdded(registry, device); } registry.addListener(this); + super.connect(); } @Deactivate @Override public void deactivate() { - devices.clear(); - disconnect(); super.deactivate(); - } - - private void disconnect() { - upnpService.getRegistry().removeListener(this); - } - - @Override - public void enable(boolean enable) { - disconnect(); - if (enable) { - connect(); - } + devices.clear(); } @Override - public String getServiceType() { - return SERVICE_TYPE; + protected void disconnect() { + upnpService.getRegistry().removeListener(this); + super.disconnect(); } @Override @@ -200,6 +198,12 @@ && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { return result; } + @Modified + @Override + public void modified(@Nullable Map configProperties) { + super.modified(configProperties); + } + /* * ************ UpnpService call-back methods ************ */ diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java index 5dd8905731b..f1295a19b47 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java @@ -104,12 +104,12 @@ private void createAddonSuggestionFinderService() { addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider); assertNotNull(addonSuggestionFinderService); - UpnpAddonSuggestionFinder upnp = new UpnpAddonSuggestionFinder(upnpService); + UpnpAddonSuggestionFinder upnp = new UpnpAddonSuggestionFinder(null, upnpService); Registry registry = upnpService.getRegistry(); registry.getRemoteDevices().forEach(device -> upnp.remoteDeviceAdded(registry, device)); addonSuggestionFinderService.addAddonSuggestionFinder(upnp); - MDNSAddonSuggestionFinder mdns = new MDNSAddonSuggestionFinder(mdnsClient); + MDNSAddonSuggestionFinder mdns = new MDNSAddonSuggestionFinder(null, mdnsClient); for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { mdns.addService(service, true); } From d8ee4324bb96fee9eeb17688ed8d9cae868d240d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 11 Nov 2023 14:29:27 +0000 Subject: [PATCH 57/98] [addon suggestion finder] fully qualify config name Signed-off-by: Andrew Fiddian-Green --- .../discovery/addon/finders/BaseAddonSuggestionFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java index 4121c6f8e0e..c3db2037f9f 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java @@ -32,7 +32,7 @@ public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder public static final String ADDON_SUGGESTION_FINDER_ENABLED_PROPERTY = "enabled"; protected static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; - protected static final String ADDON_SUGGESTION_FINDER_CONFIG_PID = "discovery.addon."; + protected static final String ADDON_SUGGESTION_FINDER_CONFIG_PID = "org.openhab.discovery.addon.finder."; /** * Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the From 850477613c1fdbd38c0237371aa02e40f5f24130 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sat, 11 Nov 2023 18:58:22 +0000 Subject: [PATCH 58/98] [addon suggestion finder] reduce logging aggressivity Signed-off-by: Andrew Fiddian-Green --- .../discovery/addon/finders/MDNSAddonSuggestionFinder.java | 5 +++-- .../discovery/addon/finders/UpnpAddonSuggestionFinder.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 73b2a17b1b9..2a806478d45 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -74,8 +74,9 @@ public MDNSAddonSuggestionFinder(@Nullable Map configProperties, public void addService(ServiceInfo service, boolean isResolved) { String qualifiedName = service.getQualifiedName(); if (isResolved || !services.containsKey(qualifiedName)) { - services.put(qualifiedName, service); - logger.trace("Added service: {}/{}", qualifiedName, service.getNiceTextString()); + if (services.put(qualifiedName, service) == null) { + logger.trace("Added service: {}/{}", qualifiedName, service.getNiceTextString()); + } } } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java index 6e82561fd8b..a4b4f941fe5 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java @@ -91,8 +91,9 @@ public void addDevice(RemoteDevice device) { UDN udn = identity.getUdn(); if (udn != null) { String udnString = udn.getIdentifierString(); - devices.put(udnString, device); - logger.trace("Added device: {}", device.getDisplayString()); + if (devices.put(udnString, device) == null) { + logger.trace("Added device: {}", device.getDisplayString()); + } } } } From 71f4214322f27e681806972ea67a42438752e856 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 21 Nov 2023 12:08:09 +0000 Subject: [PATCH 59/98] [addon suggestion finder] adopt reviewer suggestion Signed-off-by: Andrew Fiddian-Green --- .../discovery/addon/finders/MDNSAddonSuggestionFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java index 2a806478d45..029abc94eb2 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java @@ -75,7 +75,7 @@ public void addService(ServiceInfo service, boolean isResolved) { String qualifiedName = service.getQualifiedName(); if (isResolved || !services.containsKey(qualifiedName)) { if (services.put(qualifiedName, service) == null) { - logger.trace("Added service: {}/{}", qualifiedName, service.getNiceTextString()); + logger.trace("Added service: {}", qualifiedName); } } } From be30e568efb2df1f23c940bc47b5e4a2e65f123d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 23 Nov 2023 16:33:08 +0000 Subject: [PATCH 60/98] [addon suggestion finder] adopt reviewer suggestion Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/karaf/internal/KarafAddonService.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java index df8e1f33540..e3b246557ea 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java @@ -13,13 +13,7 @@ package org.openhab.core.karaf.internal; import static java.util.Map.entry; -import static org.openhab.core.addon.AddonType.AUTOMATION; -import static org.openhab.core.addon.AddonType.BINDING; -import static org.openhab.core.addon.AddonType.MISC; -import static org.openhab.core.addon.AddonType.PERSISTENCE; -import static org.openhab.core.addon.AddonType.TRANSFORMATION; -import static org.openhab.core.addon.AddonType.UI; -import static org.openhab.core.addon.AddonType.VOICE; +import static org.openhab.core.addon.AddonType.*; import java.net.URI; import java.util.Arrays; From 1d671bf62722b138a2008a3f80af37652ef55720 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 6 Nov 2023 16:39:22 +0000 Subject: [PATCH 61/98] [addoninfo] core extensions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 8 +- .../schema/addon-1.0.0.xsd | 30 ++- .../core/addon/AddonDiscoveryMethod.java | 81 +++++++ .../org/openhab/core/addon/AddonInfo.java | 36 +++- .../org/openhab/core/addon/AddonInfoList.java | 38 ++++ .../core/addon/AddonInfoListReader.java | 104 +++++++++ .../openhab/core/addon/AddonInfoProvider.java | 6 +- .../openhab/core/addon/AddonInfoRegistry.java | 81 ++++++- .../core/addon/AddonMatchProperty.java | 73 +++++++ .../internal/AddonsInfoProviderInstaller.java | 56 +++++ .../xml/AddonDiscoveryMethodConverter.java | 61 ++++++ .../internal/xml/AddonInfoConverter.java | 7 + .../internal/xml/AddonInfoListConverter.java | 57 +++++ .../addon/internal/xml/AddonInfoReader.java | 13 ++ .../xml/AddonMatchPropertyConverter.java | 52 +++++ .../addon/test/AddonInfoListReaderTest.java | 121 +++++++++++ .../test/AddonInfoRegistryMergeTest.java | 203 ++++++++++++++++++ .../core/xml/util/XmlDocumentReader.java | 17 ++ .../core/addon/xml/test/AddonInfoTest.java | 33 ++- .../OH-INF/addon/addon.xml | 17 ++ 20 files changed, 1068 insertions(+), 26 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java create mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java create mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index b5320e6c5b4..634118741ac 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -24,16 +24,16 @@ - + - - - + + + diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index 5760d5d1bdf..c4ef068ca81 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -28,7 +28,8 @@ - + + Comma-separated list of two-letter ISO country codes. @@ -95,4 +96,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java new file mode 100644 index 00000000000..88eb8669819 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonDiscoveryMethod.java @@ -0,0 +1,81 @@ +/** + * 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.addon; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * DTO for serialization of a suggested addon discovery method. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonDiscoveryMethod { + private @NonNullByDefault({}) String serviceType; + private @Nullable String mdnsServiceType; + private @Nullable List matchProperties; + + public String getServiceType() { + return serviceType.toLowerCase(); + } + + public String getMdnsServiceType() { + String mdnsServiceType = this.mdnsServiceType; + return mdnsServiceType != null ? mdnsServiceType : ""; + } + + public List getMatchProperties() { + List matchProperties = this.matchProperties; + return matchProperties != null ? matchProperties : List.of(); + } + + public AddonDiscoveryMethod setServiceType(String serviceType) { + this.serviceType = serviceType.toLowerCase(); + return this; + } + + public AddonDiscoveryMethod setMdnsServiceType(@Nullable String mdnsServiceType) { + this.mdnsServiceType = mdnsServiceType; + return this; + } + + public AddonDiscoveryMethod setMatchProperties(@Nullable List matchProperties) { + this.matchProperties = matchProperties; + return this; + } + + @Override + public int hashCode() { + return Objects.hash(serviceType, mdnsServiceType, matchProperties); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AddonDiscoveryMethod other = (AddonDiscoveryMethod) obj; + return Objects.equals(serviceType, other.serviceType) && Objects.equals(mdnsServiceType, other.mdnsServiceType) + && Objects.equals(matchProperties, other.matchProperties); + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java index 8de5d828f70..8e45c47c918 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfo.java @@ -37,6 +37,7 @@ public class AddonInfo implements Identifiable { private final String id; private final String type; + private final String uid; private final String name; private final String description; private final @Nullable String connection; @@ -44,10 +45,12 @@ public class AddonInfo implements Identifiable { private final @Nullable String configDescriptionURI; private final String serviceId; private @Nullable String sourceBundle; + private @Nullable List discoveryMethods; - private AddonInfo(String id, String type, String name, String description, @Nullable String connection, - List countries, @Nullable String configDescriptionURI, @Nullable String serviceId, - @Nullable String sourceBundle) throws IllegalArgumentException { + private AddonInfo(String id, String type, @Nullable String uid, String name, String description, + @Nullable String connection, List countries, @Nullable String configDescriptionURI, + @Nullable String serviceId, @Nullable String sourceBundle, + @Nullable List discoveryMethods) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { throw new IllegalArgumentException("The ID must neither be null nor empty!"); @@ -64,6 +67,7 @@ private AddonInfo(String id, String type, String name, String description, @Null } this.id = id; this.type = type; + this.uid = uid != null ? uid : type + Addon.ADDON_SEPARATOR + id; this.name = name; this.description = description; @@ -73,6 +77,7 @@ private AddonInfo(String id, String type, String name, String description, @Null this.configDescriptionURI = configDescriptionURI; this.serviceId = Objects.requireNonNullElse(serviceId, type + "." + id); this.sourceBundle = sourceBundle; + this.discoveryMethods = discoveryMethods; } /** @@ -82,7 +87,7 @@ private AddonInfo(String id, String type, String name, String description, @Null */ @Override public String getUID() { - return type + Addon.ADDON_SEPARATOR + id; + return uid; } /** @@ -142,6 +147,11 @@ public List getCountries() { return countries; } + public List getDiscoveryMethods() { + List discoveryMethods = this.discoveryMethods; + return discoveryMethods != null ? discoveryMethods : List.of(); + } + public static Builder builder(String id, String type) { return new Builder(id, type); } @@ -154,6 +164,7 @@ public static class Builder { private final String id; private final String type; + private @Nullable String uid; private String name = ""; private String description = ""; private @Nullable String connection; @@ -161,6 +172,7 @@ public static class Builder { private @Nullable String configDescriptionURI = ""; private @Nullable String serviceId; private @Nullable String sourceBundle; + private @Nullable List discoveryMethods; private Builder(String id, String type) { this.id = id; @@ -170,6 +182,7 @@ private Builder(String id, String type) { private Builder(AddonInfo addonInfo) { this.id = addonInfo.id; this.type = addonInfo.type; + this.uid = addonInfo.uid; this.name = addonInfo.name; this.description = addonInfo.description; this.connection = addonInfo.connection; @@ -177,6 +190,12 @@ private Builder(AddonInfo addonInfo) { this.configDescriptionURI = addonInfo.configDescriptionURI; this.serviceId = addonInfo.serviceId; this.sourceBundle = addonInfo.sourceBundle; + this.discoveryMethods = addonInfo.discoveryMethods; + } + + public Builder withUID(@Nullable String uid) { + this.uid = uid; + return this; } public Builder withName(String name) { @@ -219,6 +238,11 @@ public Builder withSourceBundle(@Nullable String sourceBundle) { return this; } + public Builder withDiscoveryMethods(@Nullable List discoveryMethods) { + this.discoveryMethods = discoveryMethods; + return this; + } + /** * Build an {@link AddonInfo} from this builder * @@ -226,8 +250,8 @@ public Builder withSourceBundle(@Nullable String sourceBundle) { * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId, - sourceBundle); + return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, + serviceId, sourceBundle, discoveryMethods); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java new file mode 100644 index 00000000000..6638d9dec49 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoList.java @@ -0,0 +1,38 @@ +/** + * 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.addon; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * DTO containing a list of {@code AddonInfo} + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonInfoList { + protected @Nullable List addons; + + public List getAddons() { + List addons = this.addons; + return addons != null ? addons : List.of(); + } + + public AddonInfoList setAddons(@Nullable List addons) { + this.addons = addons; + return this; + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java new file mode 100644 index 00000000000..393444579c1 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java @@ -0,0 +1,104 @@ +/** + * 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.addon; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.internal.xml.AddonDiscoveryMethodConverter; +import org.openhab.core.addon.internal.xml.AddonInfoConverter; +import org.openhab.core.addon.internal.xml.AddonInfoListConverter; +import org.openhab.core.addon.internal.xml.AddonInfoXmlResult; +import org.openhab.core.addon.internal.xml.AddonMatchPropertyConverter; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.ConfigDescriptionParameter; +import org.openhab.core.config.core.ConfigDescriptionParameterGroup; +import org.openhab.core.config.core.FilterCriteria; +import org.openhab.core.config.core.xml.ConfigDescriptionConverter; +import org.openhab.core.config.core.xml.ConfigDescriptionParameterConverter; +import org.openhab.core.config.core.xml.ConfigDescriptionParameterGroupConverter; +import org.openhab.core.config.core.xml.FilterCriteriaConverter; +import org.openhab.core.config.core.xml.util.NodeAttributes; +import org.openhab.core.config.core.xml.util.NodeAttributesConverter; +import org.openhab.core.config.core.xml.util.NodeList; +import org.openhab.core.config.core.xml.util.NodeListConverter; +import org.openhab.core.config.core.xml.util.NodeValue; +import org.openhab.core.config.core.xml.util.NodeValueConverter; +import org.openhab.core.config.core.xml.util.XmlDocumentReader; + +import com.thoughtworks.xstream.XStream; + +/** + * The {@link AddonInfoListReader} reads XML documents, which contain the {@code binding} XML tag, and converts them to + * a List of {@link AddonInfoXmlResult} objects. + *

+ * This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonInfoListReader extends XmlDocumentReader { + + /** + * The default constructor of this class. + */ + public AddonInfoListReader() { + ClassLoader classLoader = AddonInfoListReader.class.getClassLoader(); + if (classLoader != null) { + super.setClassLoader(classLoader); + } + } + + @Override + protected void registerConverters(XStream xstream) { + xstream.registerConverter(new NodeAttributesConverter()); + xstream.registerConverter(new NodeListConverter()); + xstream.registerConverter(new NodeValueConverter()); + xstream.registerConverter(new AddonInfoListConverter()); + xstream.registerConverter(new AddonInfoConverter()); + xstream.registerConverter(new ConfigDescriptionConverter()); + xstream.registerConverter(new ConfigDescriptionParameterConverter()); + xstream.registerConverter(new ConfigDescriptionParameterGroupConverter()); + xstream.registerConverter(new FilterCriteriaConverter()); + xstream.registerConverter(new AddonDiscoveryMethodConverter()); + xstream.registerConverter(new AddonMatchPropertyConverter()); + } + + @Override + protected void registerAliases(XStream xstream) { + xstream.alias("addon-info-list", AddonInfoList.class); + xstream.alias("addons", NodeList.class); + xstream.alias("addon", AddonInfoXmlResult.class); + xstream.alias("name", NodeValue.class); + xstream.alias("description", NodeValue.class); + xstream.alias("type", NodeValue.class); + xstream.alias("connection", NodeValue.class); + xstream.alias("countries", NodeValue.class); + xstream.alias("config-description", ConfigDescription.class); + xstream.alias("config-description-ref", NodeAttributes.class); + xstream.alias("parameter", ConfigDescriptionParameter.class); + xstream.alias("parameter-group", ConfigDescriptionParameterGroup.class); + xstream.alias("options", NodeList.class); + xstream.alias("option", NodeValue.class); + xstream.alias("filter", List.class); + xstream.alias("criteria", FilterCriteria.class); + xstream.alias("service-id", NodeValue.class); + xstream.alias("discovery-methods", NodeList.class); + xstream.alias("discovery-method", AddonDiscoveryMethod.class); + xstream.alias("service-type", NodeValue.class); + xstream.alias("mdns-service-type", NodeValue.class); + xstream.alias("match-properties", NodeList.class); + xstream.alias("match-property", AddonMatchProperty.class); + xstream.alias("regex", NodeValue.class); + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java index edd8b095883..b444451afb4 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoProvider.java @@ -31,15 +31,15 @@ public interface AddonInfoProvider { /** - * Returns the binding information for the specified binding ID and locale (language), + * Returns the binding information for the specified binding UID and locale (language), * or {@code null} if no binding information could be found. * - * @param id the ID to be looked for (could be null or empty) + * @param uid the UID to be looked for (could be null or empty) * @param locale the locale to be used for the binding information (could be null) * @return a localized binding information object (could be null) */ @Nullable - AddonInfo getAddonInfo(@Nullable String id, @Nullable Locale locale); + AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale); /** * Returns all binding information in the specified locale (language) this provider contains. diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 678c329ca2d..7b4c1e21df0 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -13,10 +13,13 @@ package org.openhab.core.addon; import java.util.Collection; +import java.util.HashSet; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BinaryOperator; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -40,38 +43,94 @@ public class AddonInfoRegistry { private final Collection addonInfoProviders = new CopyOnWriteArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); } - protected void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.remove(addonInfoProvider); } /** - * Returns the add-on information for the specified add-on ID, or {@code null} if no add-on information could be + * Returns the add-on information for the specified add-on UID, or {@code null} if no add-on information could be * found. * - * @param id the ID to be looked + * @param uid the UID to be looked * @return a add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String id) { - return getAddonInfo(id, null); + public @Nullable AddonInfo getAddonInfo(String uid) { + return getAddonInfo(uid, null); } /** - * Returns the add-on information for the specified add-on ID and locale (language), + * Returns the add-on information for the specified add-on UID and locale (language), * or {@code null} if no add-on information could be found. + *

+ * If more than one provider provides information for the specified add-on UID and locale, + * it returns a new {@link AddonInfo} containing merged information from all such providers. * - * @param id the ID to be looked for + * @param uid the UID to be looked for * @param locale the locale to be used for the add-on information (could be null) * @return a localized add-on information object (could be null) */ - public @Nullable AddonInfo getAddonInfo(String id, @Nullable Locale locale) { - return addonInfoProviders.stream().map(p -> p.getAddonInfo(id, locale)).filter(Objects::nonNull).findAny() - .orElse(null); + public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) { + return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)).filter(Objects::nonNull) + .collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(), + Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get))) + .get(uid); } + /** + * A {@link BinaryOperator} to merge the field values from two {@link AddonInfo} objects into a third such object. + *

+ * If the first object has a non-null field value the result object takes the first value, or if the second object + * has a non-null field value the result object takes the second value. Otherwise the field remains null. + * + * @param a the first {@link AddonInfo} (could be null) + * @param b the second {@link AddonInfo} (could be null) + * @return a new {@link AddonInfo} containing the combined field values (could be null) + */ + private static BinaryOperator<@Nullable AddonInfo> mergeAddonInfos = (a, b) -> { + if (a == null) { + return b; + } else if (b == null) { + return a; + } + AddonInfo.Builder builder = AddonInfo.builder(a); + if (a.getDescription().isEmpty()) { + builder.withDescription(b.getDescription()); + } + if (a.getConnection() == null && b.getConnection() != null) { + builder.withConnection(b.getConnection()); + } + Set countries = new HashSet<>(a.getCountries()); + countries.addAll(b.getCountries()); + if (!countries.isEmpty()) { + builder.withCountries(countries.stream().toList()); + } + String aConfigDescriptionURI = a.getConfigDescriptionURI(); + if (aConfigDescriptionURI == null || aConfigDescriptionURI.isEmpty() && b.getConfigDescriptionURI() != null) { + builder.withConfigDescriptionURI(b.getConfigDescriptionURI()); + } + if (a.getSourceBundle() == null && b.getSourceBundle() != null) { + builder.withSourceBundle(b.getSourceBundle()); + } + String defaultServiceId = a.getType() + "." + a.getId(); + if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) { + builder.withServiceId(b.getServiceId()); + } + String defaultUID = a.getType() + Addon.ADDON_SEPARATOR + a.getId(); + if (defaultUID.equals(a.getUID()) && !defaultUID.equals(b.getUID())) { + builder.withUID(b.getUID()); + } + Set discoveryMethods = new HashSet<>(a.getDiscoveryMethods()); + discoveryMethods.addAll(b.getDiscoveryMethods()); + if (!discoveryMethods.isEmpty()) { + builder.withDiscoveryMethods(discoveryMethods.stream().toList()); + } + return builder.build(); + }; + /** * Returns all add-on information this registry contains. * diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java new file mode 100644 index 00000000000..ac5ccebe9b3 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonMatchProperty.java @@ -0,0 +1,73 @@ +/** + * 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.addon; + +import java.util.Objects; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * DTO for serialization of a property match regular expression. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonMatchProperty { + private @NonNullByDefault({}) String name; + private @NonNullByDefault({}) String regex; + private transient @NonNullByDefault({}) Pattern pattern; + + public AddonMatchProperty(String name, String regex) { + this.name = name; + this.regex = regex; + this.pattern = null; + } + + public String getName() { + return name; + } + + public Pattern getPattern() { + Pattern pattern = this.pattern; + if (pattern == null) { + this.pattern = Pattern.compile(regex); + } + return this.pattern; + } + + public String getRegex() { + return regex; + } + + @Override + public int hashCode() { + return Objects.hash(name, regex); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AddonMatchProperty other = (AddonMatchProperty) obj; + return Objects.equals(name, other.name) && Objects.equals(regex, other.regex); + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java new file mode 100644 index 00000000000..e68fcb7c363 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java @@ -0,0 +1,56 @@ +/** + * 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.addon.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +/** + * The {@link AddonsInfoProviderInstaller} component to install the special AddonsInfoProvider addon. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, name = AddonsInfoProviderInstaller.SERVICE_NAME) +public class AddonsInfoProviderInstaller { + + public static final String SERVICE_NAME = "Installer for the AddonsInfoProvider special addon"; + + private static final String KARAF_ADDONS_SERVICE_ID = "karaf"; + private static final String ADDONS_INFO_PROVIDER_UID = "misc" + Addon.ADDON_SEPARATOR + "addonsinfoprovider"; + + private boolean addonInstalled; + + public AddonsInfoProviderInstaller() { + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonService(AddonService addonService) { + if (!addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { + addonService.install(ADDONS_INFO_PROVIDER_UID); + addonInstalled = true; + } + } + + public void removeAddonService(AddonService addonService) { + if (addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { + addonService.uninstall(ADDONS_INFO_PROVIDER_UID); + addonInstalled = false; + } + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java new file mode 100644 index 00000000000..39110721aec --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonDiscoveryMethodConverter.java @@ -0,0 +1,61 @@ +/** + * 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.addon.internal.xml; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.config.core.xml.util.GenericUnmarshaller; +import org.openhab.core.config.core.xml.util.NodeIterator; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * The {@link AddonDiscoveryMethodConverter} is a concrete implementation of the {@code XStream} {@link Converter} + * interface used to convert add-on discovery method information within an XML document into a + * {@link AddonDiscoveryMethod} object. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonDiscoveryMethodConverter extends GenericUnmarshaller { + + public AddonDiscoveryMethodConverter() { + super(AddonDiscoveryMethod.class); + } + + @Override + public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + List nodes = (List) context.convertAnother(context, List.class); + NodeIterator nodeIterator = new NodeIterator(nodes); + + String serviceType = requireNonEmpty((String) nodeIterator.nextValue("service-type", true), + "Service type is null or empty"); + + String mdnsServiceType = (String) nodeIterator.nextValue("mdns-service-type", false); + + Object object = nodeIterator.nextList("match-properties", false); + List matchProperties = !(object instanceof List list) ? null + : list.stream().filter(e -> (e instanceof AddonMatchProperty)).map(e -> ((AddonMatchProperty) e)) + .toList(); + + nodeIterator.assertEndOfType(); + + return new AddonDiscoveryMethod().setServiceType(serviceType).setMdnsServiceType(mdnsServiceType) + .setMatchProperties(matchProperties); + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java index 257dfff682f..2e1db84409a 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoConverter.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionBuilder; @@ -37,6 +38,7 @@ * @author Michael Grammling - Initial contribution * @author Andre Fuechsel - Made author tag optional * @author Jan N. Klug - Refactored to cover all add-ons + * @author Andrew Fiddian-Green - Added discovery methods */ @NonNullByDefault public class AddonInfoConverter extends GenericUnmarshaller { @@ -107,6 +109,11 @@ public AddonInfoConverter() { addonInfo.withConfigDescriptionURI(configDescriptionURI); + Object object = nodeIterator.nextList("discovery-methods", false); + addonInfo.withDiscoveryMethods(!(object instanceof List list) ? null + : list.stream().filter(e -> (e instanceof AddonDiscoveryMethod)).map(e -> ((AddonDiscoveryMethod) e)) + .toList()); + nodeIterator.assertEndOfType(); // create object diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java new file mode 100644 index 00000000000..60a93ad0027 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListConverter.java @@ -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.addon.internal.xml; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.config.core.xml.util.GenericUnmarshaller; +import org.openhab.core.config.core.xml.util.NodeIterator; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * The {@link AddonInfoListConverter} is a concrete implementation of the {@code XStream} {@link Converter} + * interface used to convert a list of add-on information within an XML document into a list of {@link AddonInfo} + * objects. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonInfoListConverter extends GenericUnmarshaller { + + public AddonInfoListConverter() { + super(AddonInfoList.class); + } + + @Override + public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + List nodes = (List) context.convertAnother(context, List.class); + NodeIterator nodeIterator = new NodeIterator(nodes); + + Object object = nodeIterator.nextList("addons", false); + List addons = (object instanceof List list) + ? list.stream().filter(e -> e != null).filter(e -> (e instanceof AddonInfoXmlResult)) + .map(e -> (AddonInfoXmlResult) e).map(r -> r.addonInfo()).toList() + : null; + + nodeIterator.assertEndOfType(); + + return new AddonInfoList().setAddons(addons); + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java index 88309b3eb59..47cad85e943 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java @@ -15,6 +15,8 @@ import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; @@ -26,6 +28,7 @@ import org.openhab.core.config.core.xml.util.NodeAttributes; import org.openhab.core.config.core.xml.util.NodeAttributesConverter; import org.openhab.core.config.core.xml.util.NodeList; +import org.openhab.core.config.core.xml.util.NodeListConverter; import org.openhab.core.config.core.xml.util.NodeValue; import org.openhab.core.config.core.xml.util.NodeValueConverter; import org.openhab.core.config.core.xml.util.XmlDocumentReader; @@ -59,12 +62,15 @@ public AddonInfoReader() { @Override protected void registerConverters(XStream xstream) { xstream.registerConverter(new NodeAttributesConverter()); + xstream.registerConverter(new NodeListConverter()); xstream.registerConverter(new NodeValueConverter()); xstream.registerConverter(new AddonInfoConverter()); xstream.registerConverter(new ConfigDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionParameterConverter()); xstream.registerConverter(new ConfigDescriptionParameterGroupConverter()); xstream.registerConverter(new FilterCriteriaConverter()); + xstream.registerConverter(new AddonDiscoveryMethodConverter()); + xstream.registerConverter(new AddonMatchPropertyConverter()); } @Override @@ -84,5 +90,12 @@ protected void registerAliases(XStream xstream) { xstream.alias("filter", List.class); xstream.alias("criteria", FilterCriteria.class); xstream.alias("service-id", NodeValue.class); + xstream.alias("discovery-methods", NodeList.class); + xstream.alias("discovery-method", AddonDiscoveryMethod.class); + xstream.alias("service-type", NodeValue.class); + xstream.alias("mdns-service-type", NodeValue.class); + xstream.alias("match-properties", NodeList.class); + xstream.alias("match-property", AddonMatchProperty.class); + xstream.alias("regex", NodeValue.class); } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java new file mode 100644 index 00000000000..f8f5321d9de --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonMatchPropertyConverter.java @@ -0,0 +1,52 @@ +/** + * 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.addon.internal.xml; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.config.core.xml.util.GenericUnmarshaller; +import org.openhab.core.config.core.xml.util.NodeIterator; + +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * The {@link AddonMatchPropertyConverter} is a concrete implementation of the {@code XStream} {@link Converter} + * interface used to convert add-on discovery method match property information within an XML document into a + * {@link AddonMatchProperty} object. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class AddonMatchPropertyConverter extends GenericUnmarshaller { + + public AddonMatchPropertyConverter() { + super(AddonMatchProperty.class); + } + + @Override + public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + List nodes = (List) context.convertAnother(context, List.class); + NodeIterator nodeIterator = new NodeIterator(nodes); + + String name = requireNonEmpty((String) nodeIterator.nextValue("name", true), "Name is null or empty"); + String regex = requireNonEmpty((String) nodeIterator.nextValue("regex", true), "Regex is null or empty"); + + nodeIterator.assertEndOfType(); + + return new AddonMatchProperty(name, regex); + } +} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java new file mode 100644 index 00000000000..bc20aaa6620 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java @@ -0,0 +1,121 @@ +/** + * 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.addon.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.addon.AddonInfoListReader; +import org.openhab.core.addon.AddonMatchProperty; + +/** + * JUnit tests for {@link AddonInfoListReader}. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +class AddonInfoListReaderTest { + + // @formatter:off + private final String testXml = + "" + + " " + + " automation" + + " Groovy Scripting" + + " This adds a Groovy script engine." + + " none" + + " " + + " " + + " mdns" + + " _printer._tcp.local." + + " " + + " " + + " rp" + + " .*" + + " " + + " " + + " ty" + + " hp (.*)" + + " " + + " " + + " " + + " " + + " upnp" + + " " + + " " + + " modelName" + + " Philips hue bridge" + + " " + + " " + + " " + + " " + + " " + + ""; + // @formatter:on + + @Test + void testAddonInfoListReader() { + AddonInfoList addons = null; + try { + AddonInfoListReader reader = new AddonInfoListReader(); + addons = reader.readFromXML(testXml); + } catch (Exception e) { + fail(e); + } + assertNotNull(addons); + List addonsInfos = addons.getAddons(); + assertEquals(1, addonsInfos.size()); + AddonInfo addon = addonsInfos.get(0); + assertNotNull(addon); + List discoveryMethods = addon.getDiscoveryMethods(); + assertNotNull(discoveryMethods); + assertEquals(2, discoveryMethods.size()); + + AddonDiscoveryMethod method = discoveryMethods.get(0); + assertNotNull(method); + assertEquals("mdns", method.getServiceType()); + assertEquals("_printer._tcp.local.", method.getMdnsServiceType()); + List matchProperties = method.getMatchProperties(); + assertNotNull(matchProperties); + assertEquals(2, matchProperties.size()); + AddonMatchProperty property = matchProperties.get(0); + assertNotNull(property); + assertEquals("rp", property.getName()); + assertEquals(".*", property.getRegex()); + assertTrue(property.getPattern().matcher("the cat sat on the mat").matches()); + + method = discoveryMethods.get(1); + assertNotNull(method); + assertEquals("upnp", method.getServiceType()); + assertEquals("", method.getMdnsServiceType()); + matchProperties = method.getMatchProperties(); + assertNotNull(matchProperties); + assertEquals(1, matchProperties.size()); + property = matchProperties.get(0); + assertNotNull(property); + assertEquals("modelName", property.getName()); + assertEquals("Philips hue bridge", property.getRegex()); + assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); + } +} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java new file mode 100644 index 00000000000..e7cb73cd6dd --- /dev/null +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java @@ -0,0 +1,203 @@ +/** + * 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.addon.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonInfoRegistry; +import org.openhab.core.addon.AddonMatchProperty; + +/** + * JUnit test for the {@link AddonInfoRegistry} merge function. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +class AddonInfoRegistryMergeTest { + + private @Nullable AddonInfoProvider addonInfoProvider0; + private @Nullable AddonInfoProvider addonInfoProvider1; + private @Nullable AddonInfoProvider addonInfoProvider2; + + @BeforeAll + void beforeAll() { + addonInfoProvider0 = createAddonInfoProvider0(); + addonInfoProvider1 = createAddonInfoProvider1(); + addonInfoProvider2 = createAddonInfoProvider2(); + } + + private AddonInfoProvider createAddonInfoProvider0() { + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero") + .withDescription("description-zero").build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + private AddonInfoProvider createAddonInfoProvider1() { + AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns") + .setMdnsServiceType("_hue._tcp.local."); + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one") + .withDescription("description-one").withCountries("GB,NL").withConnection("local") + .withDiscoveryMethods(List.of(discoveryMethod)).build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + private AddonInfoProvider createAddonInfoProvider2() { + AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("upnp") + .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") + .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") + .withServiceId("service-id").withConfigDescriptionURI("http://www.openhab.org") + .withDiscoveryMethods(List.of(discoveryMethod)).build(); + AddonInfoProvider provider = mock(AddonInfoProvider.class); + when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); + when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); + when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); + when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); + return provider; + } + + /** + * Test fetching a single addon-info from the registry with no merging. + */ + @Test + void testGetOneAddonInfo() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertNull(addonInfo.getSourceBundle()); + assertNotEquals("local", addonInfo.getConnection()); + assertEquals(0, addonInfo.getCountries().size()); + assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("binding.hue", addonInfo.getServiceId()); + assertEquals(0, addonInfo.getDiscoveryMethods().size()); + } + + /** + * Test fetching two addon-info's from the registry with merging. + */ + @Test + void testMergeAddonInfos2() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + assertNotNull(addonInfoProvider1); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertNull(addonInfo.getSourceBundle()); + assertEquals("local", addonInfo.getConnection()); + assertEquals(2, addonInfo.getCountries().size()); + assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("binding.hue", addonInfo.getServiceId()); + assertEquals(1, addonInfo.getDiscoveryMethods().size()); + } + + /** + * Test fetching three addon-info's from the registry with full merging. + */ + @Test + void testMergeAddonInfos3() { + AddonInfoRegistry registry = new AddonInfoRegistry(); + assertNotNull(addonInfoProvider0); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); + assertNotNull(addonInfoProvider1); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); + assertNotNull(addonInfoProvider2); + registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider2)); + + AddonInfo addonInfo; + addonInfo = registry.getAddonInfo("aardvark", Locale.US); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("aardvark", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", null); + assertNull(addonInfo); + addonInfo = registry.getAddonInfo("binding-hue", Locale.US); + assertNotNull(addonInfo); + + assertEquals("hue", addonInfo.getId()); + assertEquals("binding", addonInfo.getType()); + assertEquals("binding-hue", addonInfo.getUID()); + assertTrue(addonInfo.getName().startsWith("name-")); + assertTrue(addonInfo.getDescription().startsWith("description-")); + assertEquals("source-bundle", addonInfo.getSourceBundle()); + assertEquals("local", addonInfo.getConnection()); + assertEquals(4, addonInfo.getCountries().size()); + assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); + assertEquals("service-id", addonInfo.getServiceId()); + assertEquals(2, addonInfo.getDiscoveryMethods().size()); + } +} diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java index caa8bfd343a..a413fcd9930 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java @@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.Nullable; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.io.xml.StaxDriver; @@ -104,4 +105,20 @@ protected void configureSecurity(XStream xstream) { public @Nullable T readFromXML(URL xmlURL) throws ConversionException { return (@Nullable T) xstream.fromXML(xmlURL); } + + /** + * Reads the XML document containing a specific XML tag from the specified xml string and converts it to the + * according object. + *

+ * This method returns {@code null} if the given URL is {@code null}. + * + * @param xml a string containing the XML document to be read. + * @return the conversion result object (could be null). + * @throws XStreamException if the object cannot be deserialized. + * @throws ConversionException if the specified document contains invalid content + */ + @SuppressWarnings("unchecked") + public @Nullable T readFromXML(String xml) throws ConversionException { + return (@Nullable T) xstream.fromXML(xml); + } } diff --git a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java index 1531c2ab4b8..3e109b7a5a9 100644 --- a/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java +++ b/itests/org.openhab.core.addon.tests/src/main/java/org/openhab/core/addon/xml/test/AddonInfoTest.java @@ -12,8 +12,12 @@ */ package org.openhab.core.addon.xml.test; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.List; @@ -24,8 +28,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoRegistry; +import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionRegistry; @@ -66,6 +72,31 @@ public void assertThatAddonInfoIsReadProperly() throws Exception { assertThat(addonInfo.getDescription(), is("The hue Binding integrates the Philips hue system. It allows to control hue lights.")); assertThat(addonInfo.getName(), is("hue Binding")); + + List discoveryMethods = addonInfo.getDiscoveryMethods(); + assertNotNull(discoveryMethods); + assertEquals(2, discoveryMethods.size()); + + AddonDiscoveryMethod discoveryMethod = discoveryMethods.get(0); + assertNotNull(discoveryMethod); + assertEquals("mdns", discoveryMethod.getServiceType()); + assertEquals("_hue._tcp.local.", discoveryMethod.getMdnsServiceType()); + List properties = discoveryMethod.getMatchProperties(); + assertNotNull(properties); + assertEquals(0, properties.size()); + + discoveryMethod = discoveryMethods.get(1); + assertNotNull(discoveryMethod); + assertEquals("upnp", discoveryMethod.getServiceType()); + assertEquals("", discoveryMethod.getMdnsServiceType()); + properties = discoveryMethod.getMatchProperties(); + assertNotNull(properties); + assertEquals(1, properties.size()); + AddonMatchProperty property = properties.get(0); + assertNotNull(property); + assertEquals("modelName", property.getName()); + assertEquals("Philips hue bridge", property.getRegex()); + assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); }); } diff --git a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml index 4d351403227..9e5db0944bd 100644 --- a/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml +++ b/itests/org.openhab.core.addon.tests/src/main/resources/test-bundle-pool/BundleInfoTest.bundle/OH-INF/addon/addon.xml @@ -30,4 +30,21 @@ + + + + mdns + _hue._tcp.local. + + + upnp + + + modelName + Philips hue bridge + + + + + From 7533795af5cbe56e602cc944beb7a8947b0db63d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:30:45 +0000 Subject: [PATCH 62/98] [addoninfo] remove addon installer; add code to core Signed-off-by: Andrew Fiddian-Green --- .../KarafAddonsInfoProvider.java | 138 ++++++++++++++++++ .../internal/AddonsInfoProviderInstaller.java | 56 ------- 2 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java new file mode 100644 index 00000000000..eac06c23dc3 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java @@ -0,0 +1,138 @@ +/** + * 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.addon.infoproviders; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.OpenHAB; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.addon.AddonInfoListReader; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonMatchProperty; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * The {@link KarafAddonsInfoProvider} provides information from the addon.xml file of + * the addons that will are packaged in the openhab-addons .kar file. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(service = AddonInfoProvider.class, name = KarafAddonsInfoProvider.SERVICE_NAME) +public class KarafAddonsInfoProvider implements AddonInfoProvider { + + public static final String SERVICE_NAME = "karaf-addons-info-provider"; + + private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; + + private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); + private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "addons.xml"; + private final Set addonInfos = new HashSet<>(); + + @Activate + public KarafAddonsInfoProvider() { + initialize(); + if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { + testAddonDeveloperRegexSyntax(); + } + } + + @Deactivate + public void deactivate() { + addonInfos.clear(); + } + + @Override + public @Nullable AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale) { + return addonInfos.stream().filter(a -> a.getUID().equals(uid)).findFirst().orElse(null); + } + + @Override + public Set getAddonInfos(@Nullable Locale locale) { + return addonInfos; + } + + private void initialize() { + String addonsXml; + try (InputStream stream = new FileInputStream(addonsXmlPathName)) { + addonsXml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + logger.warn("The 'addons.xml' file is missing"); + return; + } + if (addonsXml.isBlank()) { + logger.warn("The 'addons.xml' file is empty"); + return; + } + try { + AddonInfoList addonInfoList = new AddonInfoListReader().readFromXML(addonsXml); + addonInfos.addAll(addonInfoList.getAddons().stream().collect(Collectors.toSet())); + } catch (ConversionException e) { + logger.warn("The 'addons.xml' file has invalid content"); + return; + } catch (XStreamException e) { + logger.warn("The 'addons.xml' file cannot be deserialized"); + return; + } + } + + /* + * The openhab-addons Maven build process checks individual developer addon.xml contributions + * against the 'addon-1.0.0.xsd' schema, but it can't check the discovery-method match-property + * regex syntax. Invalid regexes do throw exceptions at run-time, but the log can't identify the + * culprit addon. Ideally we need to add syntax checks to the Maven build; and this test is an + * interim solution. + */ + private void testAddonDeveloperRegexSyntax() { + List patternErrors = new ArrayList<>(); + for (AddonInfo addonInfo : addonInfos) { + for (AddonDiscoveryMethod discoveryMethod : addonInfo.getDiscoveryMethods()) { + for (AddonMatchProperty matchProperty : discoveryMethod.getMatchProperties()) { + try { + matchProperty.getPattern(); + } catch (PatternSyntaxException e) { + patternErrors.add(String.format( + "Regex syntax error in org.openhab.%s.%s addon.xml => %s in \"%s\" position %d", + addonInfo.getType(), addonInfo.getId(), e.getDescription(), e.getPattern(), + e.getIndex())); + } + } + } + } + if (!patternErrors.isEmpty()) { + logger.warn("The 'addons.xml' file has errors\n\t{}", String.join("\n\t", patternErrors)); + } + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java deleted file mode 100644 index e68fcb7c363..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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.addon.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.Addon; -import org.openhab.core.addon.AddonService; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; - -/** - * The {@link AddonsInfoProviderInstaller} component to install the special AddonsInfoProvider addon. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(immediate = true, name = AddonsInfoProviderInstaller.SERVICE_NAME) -public class AddonsInfoProviderInstaller { - - public static final String SERVICE_NAME = "Installer for the AddonsInfoProvider special addon"; - - private static final String KARAF_ADDONS_SERVICE_ID = "karaf"; - private static final String ADDONS_INFO_PROVIDER_UID = "misc" + Addon.ADDON_SEPARATOR + "addonsinfoprovider"; - - private boolean addonInstalled; - - public AddonsInfoProviderInstaller() { - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonService(AddonService addonService) { - if (!addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { - addonService.install(ADDONS_INFO_PROVIDER_UID); - addonInstalled = true; - } - } - - public void removeAddonService(AddonService addonService) { - if (addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { - addonService.uninstall(ADDONS_INFO_PROVIDER_UID); - addonInstalled = false; - } - } -} From 78291367bacf272523bbb3845663fe1d1f227412 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:56:50 +0000 Subject: [PATCH 63/98] [addoninfo] fix path name Signed-off-by: Andrew Fiddian-Green --- .../core/addon/infoproviders/KarafAddonsInfoProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java index eac06c23dc3..b1e9da6e55f 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java @@ -58,8 +58,9 @@ public class KarafAddonsInfoProvider implements AddonInfoProvider { private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); - private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "addons.xml"; private final Set addonInfos = new HashSet<>(); + private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "etc" + File.separator + + "addons.xml"; @Activate public KarafAddonsInfoProvider() { From 4fb6329f8a2f5324c4823a697ee9a056f5a29765 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 17:59:23 +0000 Subject: [PATCH 64/98] [addoninfo] xml schemas Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-1.0.0.xsd | 79 ++++++++----------- .../schema/addon-list-1.0.1.xsd | 24 ++++++ 2 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index c4ef068ca81..c32e01a70e4 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -7,30 +7,15 @@ - - - - - - - - - - Comma-separated list of two-letter ISO country codes. - - - - - The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is <type>.<name> - - - - - - - - - + + + + + + + + + Comma-separated list of two-letter ISO country codes. @@ -96,31 +81,31 @@ - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + + diff --git a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd new file mode 100644 index 00000000000..45ad201e918 --- /dev/null +++ b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + From ab40acbe74e2cc080ed9e9486566a8be4db344a0 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 12:43:23 +0000 Subject: [PATCH 65/98] [addoninfo] xml schemas (again) Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-list-1.0.1.xsd | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd diff --git a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd deleted file mode 100644 index 45ad201e918..00000000000 --- a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - From 2d483b658b395598d72fa2d41f6808a3fd3ebe1b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 13:43:58 +0000 Subject: [PATCH 66/98] [addoninfo] update AddonsInfoProvider Signed-off-by: Andrew Fiddian-Green --- ...oProvider.java => AddonsInfoProvider.java} | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) rename bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/{KarafAddonsInfoProvider.java => AddonsInfoProvider.java} (66%) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java similarity index 66% rename from bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java rename to bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java index b1e9da6e55f..9b21a1a2d2e 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java @@ -13,10 +13,8 @@ package org.openhab.core.addon.infoproviders; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -24,13 +22,13 @@ import java.util.Set; import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.OpenHAB; import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoList; import org.openhab.core.addon.AddonInfoListReader; import org.openhab.core.addon.AddonInfoProvider; import org.openhab.core.addon.AddonMatchProperty; @@ -44,26 +42,26 @@ import com.thoughtworks.xstream.converters.ConversionException; /** - * The {@link KarafAddonsInfoProvider} provides information from the addon.xml file of - * the addons that will are packaged in the openhab-addons .kar file. + * The {@link AddonsInfoProvider} reads all {@code userdata/addons/*.xml} files, each of which + * should contain a list of addon.xml elements, and convert their combined contents into a list + * of {@link AddonInfo} objects can be accessed via the {@link AddonInfoProvider} interface. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -@Component(service = AddonInfoProvider.class, name = KarafAddonsInfoProvider.SERVICE_NAME) -public class KarafAddonsInfoProvider implements AddonInfoProvider { +@Component(service = AddonInfoProvider.class, name = AddonsInfoProvider.SERVICE_NAME) +public class AddonsInfoProvider implements AddonInfoProvider { - public static final String SERVICE_NAME = "karaf-addons-info-provider"; + public static final String SERVICE_NAME = "addons-info-provider"; private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; - private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); + private final Logger logger = LoggerFactory.getLogger(AddonsInfoProvider.class); + private final String folder = OpenHAB.getUserDataFolder() + File.separator + "addons"; private final Set addonInfos = new HashSet<>(); - private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "etc" + File.separator - + "addons.xml"; @Activate - public KarafAddonsInfoProvider() { + public AddonsInfoProvider() { initialize(); if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { testAddonDeveloperRegexSyntax(); @@ -86,27 +84,23 @@ public Set getAddonInfos(@Nullable Locale locale) { } private void initialize() { - String addonsXml; - try (InputStream stream = new FileInputStream(addonsXmlPathName)) { - addonsXml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - logger.warn("The 'addons.xml' file is missing"); - return; - } - if (addonsXml.isBlank()) { - logger.warn("The 'addons.xml' file is empty"); - return; - } - try { - AddonInfoList addonInfoList = new AddonInfoListReader().readFromXML(addonsXml); - addonInfos.addAll(addonInfoList.getAddons().stream().collect(Collectors.toSet())); - } catch (ConversionException e) { - logger.warn("The 'addons.xml' file has invalid content"); - return; - } catch (XStreamException e) { - logger.warn("The 'addons.xml' file cannot be deserialized"); - return; - } + AddonInfoListReader reader = new AddonInfoListReader(); + Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { + try { + String xml = Files.readString(f.toPath()); + if (!xml.isBlank()) { + addonInfos.addAll(reader.readFromXML(xml).getAddons().stream().collect(Collectors.toSet())); + } else { + logger.warn("File '{}' is empty", f.getName()); + } + } catch (IOException e) { + logger.warn("File '{}' could not be read", f.getName()); + } catch (ConversionException e) { + logger.warn("File '{}' has invalid content", f.getName()); + } catch (XStreamException e) { + logger.warn("File '{}' could not deserialized", f.getName()); + } + }); } /* @@ -133,7 +127,7 @@ private void testAddonDeveloperRegexSyntax() { } } if (!patternErrors.isEmpty()) { - logger.warn("The 'addons.xml' file has errors\n\t{}", String.join("\n\t", patternErrors)); + logger.warn("The following errors were found\n\t{}", String.join("\n\t", patternErrors)); } } } From db7c5838e6851959df1a0ffe330b8d5381ac8ac2 Mon Sep 17 00:00:00 2001 From: Mark Herwege Date: Mon, 20 Nov 2023 21:42:46 +0100 Subject: [PATCH 67/98] finders karaf feature install Signed-off-by: Mark Herwege --- bom/openhab-core/pom.xml | 12 + .../org.openhab.core.addon.eclipse/pom.xml | 5 + .../.classpath | 23 ++ .../.project | 23 ++ .../NOTICE | 14 ++ .../pom.xml | 34 +++ .../addon/mdns/MdnsAddonFinder.java} | 90 +++---- .../mdns/tests/MdnsAddonFinderTests.java | 125 ++++++++++ .../.classpath | 23 ++ .../.project | 23 ++ .../pom.xml | 29 +++ .../addon/upnp/UpnpAddonFinder.java} | 75 +++--- .../upnp/tests/UpnpAddonFinderTests.java | 133 ++++++++++ .../pom.xml | 16 +- ...SuggestionFinder.java => AddonFinder.java} | 13 +- .../discovery/addon/AddonFinderConstants.java | 46 ++++ .../discovery/addon/AddonFinderService.java | 45 ++++ .../addon/AddonSuggestionFinderService.java | 98 -------- .../addon/AddonSuggestionService.java | 200 +++++++++++++++ .../discovery/addon/BaseAddonFinder.java | 60 +++++ .../finders/BaseAddonSuggestionFinder.java | 133 ---------- .../AddonSuggestionFinderServiceTests.java | 235 ------------------ .../tests/AddonSuggestionServiceTests.java | 196 +++++++++++++++ .../core/internal/addons/AddonResource.java | 10 +- bundles/org.openhab.core.karaf/.classpath | 1 + bundles/org.openhab.core.karaf/pom.xml | 5 + .../internal/KarafAddonFinderService.java | 73 ++++++ .../main/resources/OH-INF/config/addons.xml | 10 + bundles/pom.xml | 2 + features/.project | 17 ++ features/karaf/.project | 17 ++ .../openhab-core/src/main/feature/feature.xml | 19 +- 32 files changed, 1223 insertions(+), 582 deletions(-) create mode 100644 bundles/org.openhab.core.config.discovery.addon.mdns/.classpath create mode 100644 bundles/org.openhab.core.config.discovery.addon.mdns/.project create mode 100644 bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE create mode 100644 bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml rename bundles/{org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java => org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java} (78%) create mode 100644 bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java create mode 100644 bundles/org.openhab.core.config.discovery.addon.upnp/.classpath create mode 100644 bundles/org.openhab.core.config.discovery.addon.upnp/.project create mode 100644 bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml rename bundles/{org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java => org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java} (87%) create mode 100644 bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java rename bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/{finders/AddonSuggestionFinder.java => AddonFinder.java} (76%) create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java delete mode 100644 bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java create mode 100644 bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionServiceTests.java create mode 100644 bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java create mode 100644 features/.project create mode 100644 features/karaf/.project diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml index 349e93c04d9..4a96fcf9a91 100644 --- a/bom/openhab-core/pom.xml +++ b/bom/openhab-core/pom.xml @@ -310,6 +310,18 @@ ${project.version} compile + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon.mdns + ${project.version} + compile + + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon.upnp + ${project.version} + compile + org.openhab.core.bundles org.openhab.core.config.discovery.mdns diff --git a/bundles/org.openhab.core.addon.eclipse/pom.xml b/bundles/org.openhab.core.addon.eclipse/pom.xml index 1d5c0f10f8f..0a1d3707294 100644 --- a/bundles/org.openhab.core.addon.eclipse/pom.xml +++ b/bundles/org.openhab.core.addon.eclipse/pom.xml @@ -25,6 +25,11 @@ org.openhab.core.addon ${project.version} + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath b/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath new file mode 100644 index 00000000000..8084e956b45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/.project b/bundles/org.openhab.core.config.discovery.addon.mdns/.project new file mode 100644 index 00000000000..00932f9e051 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.config.discovery.addon.mdns + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE b/bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml b/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml new file mode 100644 index 00000000000..3afd5faef7d --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml @@ -0,0 +1,34 @@ + + + + 4.0.0 + + + org.openhab.core.bundles + org.openhab.core.reactor.bundles + 4.1.0-SNAPSHOT + + + org.openhab.core.config.discovery.addon.mdns + + openHAB Core :: Bundles :: mDNS Suggested Add-on Finder + + + + org.openhab.core.bundles + org.openhab.core.config.discovery.mdns + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.addon + ${project.version} + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java similarity index 78% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java rename to bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java index 029abc94eb2..90e3e3b2da1 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/MDNSAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.finders; +package org.openhab.core.config.discovery.addon.mdns; + +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*; import java.util.HashSet; import java.util.List; @@ -30,58 +32,58 @@ import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.BaseAddonFinder; import org.openhab.core.io.transport.mdns.MDNSClient; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This is a {@link MDNSAddonSuggestionFinder} for finding suggested Addons via MDNS. + * This is a {@link MdnsAddonFinder} for finding suggested Addons via MDNS. * * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - refactor to allow uninstall */ @NonNullByDefault -@Component(service = AddonSuggestionFinder.class, name = MDNSAddonSuggestionFinder.SERVICE_NAME, configurationPid = MDNSAddonSuggestionFinder.CONFIG_PID) -public class MDNSAddonSuggestionFinder extends BaseAddonSuggestionFinder implements ServiceListener { +@Component(service = AddonFinder.class, name = MdnsAddonFinder.SERVICE_NAME) +public class MdnsAddonFinder extends BaseAddonFinder implements ServiceListener { - public static final String SERVICE_TYPE = "mdns"; - public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; - public static final String CONFIG_PID = ADDON_SUGGESTION_FINDER_CONFIG_PID + SERVICE_TYPE; + public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS; + public static final String SERVICE_NAME = SERVICE_NAME_MDNS; private static final String NAME = "name"; private static final String APPLICATION = "application"; - private final Logger logger = LoggerFactory.getLogger(MDNSAddonSuggestionFinder.class); + private final Logger logger = LoggerFactory.getLogger(MdnsAddonFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Map services = new ConcurrentHashMap<>(); - private final MDNSClient mdnsClient; + private MDNSClient mdnsClient; @Activate - public MDNSAddonSuggestionFinder(@Nullable Map configProperties, @Reference MDNSClient mdnsClient) { + public MdnsAddonFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; - activate(configProperties); } - /** - * Adds the given mDNS service to the set of discovered services. - * - * @param device the mDNS service to be added. - */ - public void addService(ServiceInfo service, boolean isResolved) { - String qualifiedName = service.getQualifiedName(); - if (isResolved || !services.containsKey(qualifiedName)) { - if (services.put(qualifiedName, service) == null) { - logger.trace("Added service: {}", qualifiedName); - } - } + @Deactivate + public void deactivate() { + services.clear(); + unsetAddonCandidates(); } @Override - protected void connect() { + public void setAddonCandidates(List candidates) { + // Remove listeners for all service types that are no longer in candidates + addonCandidates.stream().filter(c -> !candidates.contains(c)) + .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) + .filter(m -> !m.getMdnsServiceType().isEmpty()) + .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); + + // Add listeners for all service types in candidates + super.setAddonCandidates(candidates); addonCandidates .forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType())) .filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> { @@ -89,22 +91,28 @@ protected void connect() { mdnsClient.addServiceListener(serviceType, this); scheduler.submit(() -> mdnsClient.list(serviceType)); })); - super.connect(); } - @Deactivate @Override - public void deactivate() { - super.deactivate(); - services.clear(); - } - - @Override - protected void disconnect() { + public void unsetAddonCandidates() { addonCandidates.forEach(c -> c.getDiscoveryMethods().stream() .filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty()) .forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this))); - super.disconnect(); + super.unsetAddonCandidates(); + } + + /** + * Adds the given mDNS service to the set of discovered services. + * + * @param device the mDNS service to be added. + */ + public void addService(ServiceInfo service, boolean isResolved) { + String qualifiedName = service.getQualifiedName(); + if (isResolved || !services.containsKey(qualifiedName)) { + if (services.put(qualifiedName, service) == null) { + logger.trace("Added service: {}", qualifiedName); + } + } } @Override @@ -139,10 +147,9 @@ && propertyMatches(matchProperties, APPLICATION, service.getApplication()) return result; } - @Modified @Override - public void modified(@Nullable Map configProperties) { - super.modified(configProperties); + public String getServiceName() { + return SERVICE_NAME; } /* @@ -172,11 +179,4 @@ public void serviceResolved(@Nullable ServiceEvent event) { } } } - - @Override - public void setAddonCandidates(List candidates) { - disconnect(); - super.setAddonCandidates(candidates); - connect(); - } } diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java new file mode 100644 index 00000000000..0c5f344a3cb --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java @@ -0,0 +1,125 @@ +/** + * 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.config.discovery.addon.mdns.tests; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.jmdns.ServiceInfo; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.mockito.Mockito; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.AddonFinderConstants; +import org.openhab.core.config.discovery.addon.AddonSuggestionService; +import org.openhab.core.config.discovery.addon.mdns.MdnsAddonFinder; +import org.openhab.core.io.transport.mdns.MDNSClient; + +/** + * JUnit tests for the {@link AddonSuggestionService}. + * + * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - Adapted to finders in separate packages + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +public class MdnsAddonFinderTests { + + private @NonNullByDefault({}) MDNSClient mdnsClient; + private @NonNullByDefault({}) AddonFinder addonFinder; + private List addonInfos = new ArrayList<>(); + + @BeforeAll + public void setup() { + setupMockMdnsClient(); + setupAddonInfos(); + createAddonFinder(); + } + + private void createAddonFinder() { + MdnsAddonFinder mdnsAddonFinder = new MdnsAddonFinder(mdnsClient); + assertNotNull(mdnsAddonFinder); + + for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { + mdnsAddonFinder.addService(service, true); + } + for (ServiceInfo service : mdnsClient.list("_printer._tcp.local.")) { + mdnsAddonFinder.addService(service, true); + } + + addonFinder = mdnsAddonFinder; + } + + private void setupMockMdnsClient() { + // create the mock + mdnsClient = mock(MDNSClient.class, Mockito.RETURNS_DEEP_STUBS); + when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {}); + ServiceInfo hueService = ServiceInfo.create("hue", "hue", 0, 0, 0, false, "hue service"); + when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService }); + ServiceInfo hpService = ServiceInfo.create("printer", "hpprinter", 0, 0, 0, false, "hp printer service"); + hpService.setText(Map.of("ty", "hp printer", "rp", "anything")); + when(mdnsClient.list(eq("_printer._tcp.local."))).thenReturn(new ServiceInfo[] { hpService }); + + // check that it works + assertNotNull(mdnsClient); + ServiceInfo[] result; + result = mdnsClient.list("_printer._tcp.local."); + assertEquals(1, result.length); + assertEquals("hpprinter", result[0].getName()); + assertEquals(2, Collections.list(result[0].getPropertyNames()).size()); + assertEquals("hp printer", result[0].getPropertyString("ty")); + result = mdnsClient.list("_hue._tcp.local."); + assertEquals(1, result.length); + assertEquals("hue", result[0].getName()); + result = mdnsClient.list("aardvark"); + assertEquals(0, result.length); + } + + private void setupAddonInfos() { + AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS) + .setMatchProperties( + List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)"))) + .setMdnsServiceType("_printer._tcp.local."); + addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer") + .withDiscoveryMethods(List.of(hp)).build()); + + AddonDiscoveryMethod hue = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS) + .setMdnsServiceType("_hue._tcp.local."); + addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") + .withDiscoveryMethods(List.of(hue)).build()); + } + + @Test + public void testGetSuggestedAddons() { + addonFinder.setAddonCandidates(addonInfos); + Set addons = addonFinder.getSuggestedAddons(); + assertEquals(2, addons.size()); + assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath b/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath new file mode 100644 index 00000000000..8084e956b45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/.project b/bundles/org.openhab.core.config.discovery.addon.upnp/.project new file mode 100644 index 00000000000..f86bba378d1 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/.project @@ -0,0 +1,23 @@ + + + org.openhab.core.config.discovery.addon.upnp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml b/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml new file mode 100644 index 00000000000..47f50aa76f6 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + org.openhab.core.bundles + org.openhab.core.reactor.bundles + 4.1.0-SNAPSHOT + + + org.openhab.core.config.discovery.addon.upnp + + openHAB Core :: Bundles :: uPnP Suggested Add-on Finder + + + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + + + org.openhab.core.bundles + org.openhab.core.addon + ${project.version} + + + diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java similarity index 87% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java rename to bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java index a4b4f941fe5..65905246fe2 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/UpnpAddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.finders; +package org.openhab.core.config.discovery.addon.upnp; + +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*; import java.net.URI; import java.util.HashSet; @@ -35,26 +37,27 @@ import org.jupnp.registry.RegistryListener; import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.BaseAddonFinder; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This is a {@link UpnpAddonSuggestionFinder} for finding suggested Addons via UPnP. + * This is a {@link UpnpAddonFinder} for finding suggested Addons via UPnP. * * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - refactor to allow uninstall */ @NonNullByDefault -@Component(service = AddonSuggestionFinder.class, name = UpnpAddonSuggestionFinder.SERVICE_NAME, configurationPid = UpnpAddonSuggestionFinder.CONFIG_PID) -public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder implements RegistryListener { +@Component(service = AddonFinder.class, name = UpnpAddonFinder.SERVICE_NAME) +public class UpnpAddonFinder extends BaseAddonFinder implements RegistryListener { - public static final String SERVICE_TYPE = "upnp"; - public static final String SERVICE_NAME = SERVICE_TYPE + ADDON_SUGGESTION_FINDER; - public static final String CONFIG_PID = ADDON_SUGGESTION_FINDER_CONFIG_PID + SERVICE_TYPE; + public static final String SERVICE_TYPE = SERVICE_TYPE_UPNP; + public static final String SERVICE_NAME = SERVICE_NAME_UPNP; private static final String DEVICE_TYPE = "deviceType"; private static final String MANUFACTURER = "manufacturer"; @@ -69,23 +72,37 @@ public class UpnpAddonSuggestionFinder extends BaseAddonSuggestionFinder impleme private static final Set SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI, MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URI, SERIAL_NUMBER, FRIENDLY_NAME); - private final Logger logger = LoggerFactory.getLogger(UpnpAddonSuggestionFinder.class); + private final Logger logger = LoggerFactory.getLogger(UpnpAddonFinder.class); private final Map devices = new ConcurrentHashMap<>(); - private final UpnpService upnpService; + private UpnpService upnpService; @Activate - public UpnpAddonSuggestionFinder(@Nullable Map configProperties, - @Reference UpnpService upnpService) { + public UpnpAddonFinder(@Reference UpnpService upnpService) { this.upnpService = upnpService; - activate(configProperties); + + Registry registry = upnpService.getRegistry(); + for (RemoteDevice device : registry.getRemoteDevices()) { + remoteDeviceAdded(registry, device); + } + registry.addListener(this); + } + + @Deactivate + public void deactivate() { + unsetAddonCandidates(); + + UpnpService upnpService = this.upnpService; + upnpService.getRegistry().removeListener(this); + + devices.clear(); } /** * Adds the given UPnP remote device to the set of discovered devices. - * + * * @param device the UPnP remote device to be added. */ - public void addDevice(RemoteDevice device) { + private void addDevice(RemoteDevice device) { RemoteDeviceIdentity identity = device.getIdentity(); if (identity != null) { UDN udn = identity.getUdn(); @@ -98,29 +115,6 @@ public void addDevice(RemoteDevice device) { } } - @Override - protected void connect() { - Registry registry = upnpService.getRegistry(); - for (RemoteDevice device : registry.getRemoteDevices()) { - remoteDeviceAdded(registry, device); - } - registry.addListener(this); - super.connect(); - } - - @Deactivate - @Override - public void deactivate() { - super.deactivate(); - devices.clear(); - } - - @Override - protected void disconnect() { - upnpService.getRegistry().removeListener(this); - super.disconnect(); - } - @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); @@ -199,10 +193,9 @@ && propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) { return result; } - @Modified @Override - public void modified(@Nullable Map configProperties) { - super.modified(configProperties); + public String getServiceName() { + return SERVICE_NAME; } /* diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java new file mode 100644 index 00000000000..2ea0296d810 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java @@ -0,0 +1,133 @@ +package org.openhab.core.config.discovery.addon.upnp.tests; + +/** + * 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 + */ + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.jupnp.UpnpService; +import org.jupnp.model.ValidationException; +import org.jupnp.model.meta.DeviceDetails; +import org.jupnp.model.meta.ManufacturerDetails; +import org.jupnp.model.meta.ModelDetails; +import org.jupnp.model.meta.RemoteDevice; +import org.jupnp.model.meta.RemoteDeviceIdentity; +import org.jupnp.model.meta.RemoteService; +import org.jupnp.model.types.DeviceType; +import org.jupnp.model.types.UDN; +import org.mockito.Mockito; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.AddonFinderConstants; +import org.openhab.core.config.discovery.addon.upnp.UpnpAddonFinder; + +/** + * JUnit tests for the {@link UpnpAddonFinder}. + * + * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - Adapted to finders in separate packages + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +public class UpnpAddonFinderTests { + + private @NonNullByDefault({}) UpnpService upnpService; + private @NonNullByDefault({}) AddonFinder addonFinder; + private List addonInfos = new ArrayList<>(); + + @BeforeAll + public void setup() { + setupMockUpnpService(); + setupAddonInfos(); + createAddonFinder(); + } + + private void createAddonFinder() { + UpnpAddonFinder upnpAddonFinder = new UpnpAddonFinder(upnpService); + assertNotNull(upnpAddonFinder); + + addonFinder = upnpAddonFinder; + } + + private void setupMockUpnpService() { + // create the mock + upnpService = mock(UpnpService.class, Mockito.RETURNS_DEEP_STUBS); + URL url = null; + try { + url = new URL("http://www.openhab.org/"); + } catch (MalformedURLException e) { + fail("MalformedURLException"); + } + UDN udn = new UDN("udn"); + InetAddress address = null; + try { + address = InetAddress.getByName("127.0.0.1"); + } catch (UnknownHostException e) { + fail("UnknownHostException"); + } + RemoteDeviceIdentity identity = new RemoteDeviceIdentity(udn, 0, url, new byte[] {}, address); + DeviceType type = new DeviceType("nameSpace", "type"); + ManufacturerDetails manDetails = new ManufacturerDetails("manufacturer", "manufacturerURI"); + ModelDetails modDetails = new ModelDetails("Philips hue bridge", "modelDescription", "modelNumber", "modelURI"); + DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber", + "000123456789"); + List<@Nullable RemoteDevice> remoteDevices = new ArrayList<>(); + try { + remoteDevices.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null)); + } catch (ValidationException e1) { + fail("ValidationException"); + } + when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevices); + + // check that it works + assertNotNull(upnpService); + List result = new ArrayList<>(upnpService.getRegistry().getRemoteDevices()); + assertEquals(1, result.size()); + RemoteDevice device = result.get(0); + assertEquals("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()); + assertEquals("serialNumber", device.getDetails().getSerialNumber()); + } + + private void setupAddonInfos() { + AddonDiscoveryMethod hue = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_UPNP) + .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); + addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") + .withDiscoveryMethods(List.of(hue)).build()); + } + + @Test + public void testGetSuggestedAddons() { + addonFinder.setAddonCandidates(addonInfos); + Set addons = addonFinder.getSuggestedAddons(); + assertEquals(1, addons.size()); + assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml index 3af4946dfe3..9b33fcd4394 100644 --- a/bundles/org.openhab.core.config.discovery.addon/pom.xml +++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml @@ -12,27 +12,13 @@ org.openhab.core.config.discovery.addon - openHAB Core :: Bundles :: Suggested Addon Finder + openHAB Core :: Bundles :: Add-on Suggestion Service - - org.openhab.core.bundles - org.openhab.core.config.discovery.mdns - ${project.version} - org.openhab.core.bundles org.openhab.core.addon ${project.version} - - - - - src/main/resources - - - - diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java similarity index 76% rename from bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java rename to bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java index 3cb727b53c6..34af610a294 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/AddonSuggestionFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.config.discovery.addon.finders; +package org.openhab.core.config.discovery.addon; import java.util.List; import java.util.Set; @@ -19,12 +19,12 @@ import org.openhab.core.addon.AddonInfo; /** - * This is a {@link AddonSuggestionFinder} interface for classes that find Addons that are suggested to be installed. + * This is a {@link AddonFinder} interface for classes that find Addons that are suggested to be installed. * * @author Andrew Fiddian-Green - Initial contribution */ @NonNullByDefault -public interface AddonSuggestionFinder { +public interface AddonFinder { /** * The OH framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of @@ -36,8 +36,13 @@ public interface AddonSuggestionFinder { * The OH framework calls this method to provide a list of {@link AddonInfo} elements which contain potential * candidates that this finder can iterate over in order to detect which ones to return via the * {@code getSuggestedAddons()} method. - * + * * @param candidates a list of AddonInfo candidates. */ public void setAddonCandidates(List candidates); + + /** + * This method should be called from the framework to allow a finder to stop searching for addons and do cleanup. + */ + public void unsetAddonCandidates(); } diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java new file mode 100644 index 00000000000..596498898d5 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java @@ -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.config.discovery.addon; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This {@link AddonFinderConstants} contains constants describing addon finders available in core. + * + * @author Mark Herwege - Initial contribution + */ +@NonNullByDefault +public class AddonFinderConstants { + + private static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; + private static final String ADDON_SUGGESTION_FINDER_FEATURE = "openhab-core-config-discovery-addon-"; + + public static final String SERVICE_TYPE_MDNS = "mdns"; + public static final String CFG_FINDER_MDNS = "suggestionFinderMdns"; + public static final String SERVICE_NAME_MDNS = SERVICE_TYPE_MDNS + ADDON_SUGGESTION_FINDER; + public static final String FEATURE_MDNS = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_MDNS; + + public static final String SERVICE_TYPE_UPNP = "upnp"; + public static final String CFG_FINDER_UPNP = "suggestionFinderUpnp"; + public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER; + public static final String FEATURE_UPNP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_UPNP; + + public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_MDNS, SERVICE_NAME_UPNP); + public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_MDNS, CFG_FINDER_MDNS, + SERVICE_NAME_UPNP, CFG_FINDER_UPNP); + public static final Map SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_MDNS, FEATURE_MDNS, + SERVICE_NAME_UPNP, FEATURE_UPNP); +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java new file mode 100644 index 00000000000..677899593b8 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java @@ -0,0 +1,45 @@ +/** + * 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.config.discovery.addon; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Classes implementing this interface can be registered as an OSGi service in order to provide functionality for + * managing add-on suggestion finders, such as installing and uninstalling them. + * + * @author Mark Herwege - Initial contribution + */ +@NonNullByDefault +public interface AddonFinderService { + + /** + * Installs the given add-on suggestion finder. + * + * This can be a long running process. The framework makes sure that this is called within a separate thread and + * add-on events will be sent upon its completion. + * + * @param id the id of the add-on suggestion finder to install + */ + void install(String id); + + /** + * Uninstalls the given add-on suggestion finder. + * + * This can be a long running process. The framework makes sure that this is called within a separate thread and + * add-on events will be sent upon its completion. + * + * @param id the id of the add-on suggestion finder to uninstall + */ + void uninstall(String id); +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java deleted file mode 100644 index 975b3a3d2a6..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionFinderService.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * 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.config.discovery.addon; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.config.discovery.addon.finders.AddonSuggestionFinder; -import org.openhab.core.i18n.LocaleProvider; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; - -/** - * This is a {@link AddonSuggestionFinderService} which discovers suggested Addons for the user to install. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(immediate = true, service = AddonSuggestionFinderService.class, name = AddonSuggestionFinderService.SERVICE_NAME) -public class AddonSuggestionFinderService implements AutoCloseable { - - public static final String SERVICE_NAME = "addon-suggestion-finder-service"; - - private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); - private final List addonSuggestionFinders = Collections.synchronizedList(new ArrayList<>()); - private final LocaleProvider localeProvider; - - @Activate - public AddonSuggestionFinderService(@Reference LocaleProvider localeProvider) { - this.localeProvider = localeProvider; - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - addonInfoProviders.add(addonInfoProvider); - changed(); - } - - public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { - if (addonInfoProviders.remove(addonInfoProvider)) { - changed(); - } - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { - addonSuggestionFinders.add(addonSuggestionFinder); - changed(); - } - - public void removeAddonSuggestionFinder(AddonSuggestionFinder addonSuggestionFinder) { - if (addonSuggestionFinders.remove(addonSuggestionFinder)) { - changed(); - } - } - - private void changed() { - List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) - .flatMap(Collection::stream).toList(); - addonSuggestionFinders.forEach(f -> f.setAddonCandidates(candidates)); - } - - @Deactivate - @Override - public void close() throws Exception { - addonSuggestionFinders.clear(); - addonInfoProviders.clear(); - } - - public Set getSuggestedAddons(@Nullable Locale locale) { - return addonSuggestionFinders.stream().map(f -> f.getSuggestedAddons()).flatMap(Collection::stream) - .collect(Collectors.toSet()); - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java new file mode 100644 index 00000000000..243e0e777fa --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java @@ -0,0 +1,200 @@ +/** + * 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.config.discovery.addon; + +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.config.core.ConfigParser; +import org.openhab.core.i18n.LocaleProvider; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * This is a {@link AddonSuggestionService} which discovers suggested Addons for the user to install. + * + * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - Install/remove finders + */ +@NonNullByDefault +@Component(immediate = true, service = AddonSuggestionService.class, name = AddonSuggestionService.SERVICE_NAME, configurationPid = AddonSuggestionService.CONFIG_PID) +public class AddonSuggestionService implements AutoCloseable { + + public static final String SERVICE_NAME = "addon-suggestion-service"; + public static final String CONFIG_PID = "org.openhab.addons"; + + private final Set addonInfoProviders = ConcurrentHashMap.newKeySet(); + private final List addonFinders = Collections.synchronizedList(new ArrayList<>()); + private final ConfigurationAdmin configurationAdmin; + private final LocaleProvider localeProvider; + private @Nullable AddonFinderService addonFinderService; + private @Nullable Map config; + private final ScheduledExecutorService scheduler; + + private Map baseFinderConfig = new ConcurrentHashMap<>(); + + @Activate + public AddonSuggestionService(final @Reference ConfigurationAdmin configurationAdmin, + @Reference LocaleProvider localeProvider, @Nullable Map config) { + this.configurationAdmin = configurationAdmin; + this.localeProvider = localeProvider; + + SUGGESTION_FINDERS.forEach(f -> baseFinderConfig.put(f, true)); + modified(config); + changed(); + + // Changes to the configuration are expected to call the {@link Modified} method. This works well when running + // in Eclipse. Running in Karaf, the method was not consistently called. Therefore regularly check for changes + // in configuration. + // This pattern and code was re-used from {@link org.openhab.core.karaf.internal.FeatureInstaller} + scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("addon-suggestion-finder")); + scheduler.scheduleWithFixedDelay(this::syncConfiguration, 1, 1, TimeUnit.MINUTES); + } + + @Deactivate + protected void deactivate() { + scheduler.shutdown(); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + protected void addAddonFinderService(AddonFinderService addonFinderService) { + this.addonFinderService = addonFinderService; + modified(config); + } + + protected void removeAddonFinderService(AddonFinderService addonFinderService) { + AddonFinderService finderService = this.addonFinderService; + if ((finderService != null) && addonFinderService.getClass().isAssignableFrom(finderService.getClass())) { + this.addonFinderService = null; + } + } + + @Modified + public void modified(@Nullable final Map config) { + baseFinderConfig.forEach((finder, cfg) -> { + String cfgParam = SUGGESTION_FINDER_CONFIGS.get(finder); + if (cfgParam != null) { + boolean enabled = (config != null) + ? ConfigParser.valueAsOrElse(config.get(cfgParam), Boolean.class, cfg) + : cfg; + baseFinderConfig.put(finder, enabled); + String feature = SUGGESTION_FINDER_FEATURES.get(finder); + AddonFinderService finderService = addonFinderService; + if (feature != null && finderService != null) { + if (enabled) { + finderService.install(feature); + } else { + finderService.uninstall(feature); + } + } + } + }); + this.config = config; + } + + private void syncConfiguration() { + try { + Dictionary cfg = configurationAdmin.getConfiguration(CONFIG_PID).getProperties(); + if (cfg == null) { + return; + } + final Map cfgMap = new HashMap<>(); + final Enumeration enumeration = cfg.keys(); + while (enumeration.hasMoreElements()) { + final String key = enumeration.nextElement(); + cfgMap.put(key, cfg.get(key)); + } + if (!cfgMap.equals(config)) { + modified(cfgMap); + } + } catch (IOException e) { + } + } + + private boolean isFinderEnabled(AddonFinder finder) { + if (finder instanceof BaseAddonFinder baseFinder) { + return baseFinderConfig.getOrDefault(baseFinder.getServiceName(), true); + } + return true; + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + addonInfoProviders.add(addonInfoProvider); + changed(); + } + + public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + if (addonInfoProviders.remove(addonInfoProvider)) { + changed(); + } + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonFinder(AddonFinder addonFinder) { + addonFinders.add(addonFinder); + changed(); + } + + public void removeAddonFinder(AddonFinder addonFinder) { + if (addonFinders.remove(addonFinder)) { + changed(); + } + } + + private void changed() { + List candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale())) + .flatMap(Collection::stream).toList(); + addonFinders.stream().filter(this::isFinderEnabled).forEach(f -> f.setAddonCandidates(candidates)); + } + + @Deactivate + @Override + public void close() throws Exception { + addonFinders.clear(); + addonInfoProviders.clear(); + } + + public Set getSuggestedAddons(@Nullable Locale locale) { + return addonFinders.stream().filter(this::isFinderEnabled).map(f -> f.getSuggestedAddons()) + .flatMap(Collection::stream).collect(Collectors.toSet()); + } +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java new file mode 100644 index 00000000000..655108cc12b --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java @@ -0,0 +1,60 @@ +/** + * 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.config.discovery.addon; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.addon.AddonInfo; + +/** + * This is a {@link BaseAddonFinder} abstract class for finding suggested Addons. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public abstract class BaseAddonFinder implements AddonFinder { + + /** + * Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the + * given {@code propertyValue} matches the respective regular expression {@code Pattern}. + * + * @param propertyPatternMap map of property names and regex patterns for value matching + * @param propertyName + * @param propertyValue + * @return true a) if the property name exists and the property value is not null and matches the regular + * expression, or b) the property name does not exist. + */ + protected static boolean propertyMatches(Map propertyPatternMap, String propertyName, + @Nullable String propertyValue) { + Pattern pattern = propertyPatternMap.get(propertyName); + return pattern == null ? true : propertyValue == null ? false : pattern.matcher(propertyValue).matches(); + } + + protected volatile List addonCandidates = List.of(); + + @Override + public void setAddonCandidates(List candidates) { + addonCandidates = candidates; + } + + @Override + public void unsetAddonCandidates() { + addonCandidates = List.of(); + } + + public abstract String getServiceName(); +} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java deleted file mode 100644 index c3db2037f9f..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/finders/BaseAddonSuggestionFinder.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * 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.config.discovery.addon.finders; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.addon.AddonInfo; - -/** - * This is a {@link BaseAddonSuggestionFinder} abstract class for finding suggested Addons. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -public abstract class BaseAddonSuggestionFinder implements AddonSuggestionFinder { - - public static final String ADDON_SUGGESTION_FINDER_ENABLED_PROPERTY = "enabled"; - protected static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder"; - protected static final String ADDON_SUGGESTION_FINDER_CONFIG_PID = "org.openhab.discovery.addon.finder."; - - /** - * Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the - * given {@code propertyValue} matches the respective regular expression {@code Pattern}. - * - * @param propertyPatternMap map of property names and regex patterns for value matching - * @param propertyName - * @param propertyValue - * @return true a) if the property name exists and the property value is not null and matches the regular - * expression, or b) the property name does not exist. - */ - protected static boolean propertyMatches(Map propertyPatternMap, String propertyName, - @Nullable String propertyValue) { - Pattern pattern = propertyPatternMap.get(propertyName); - return pattern == null ? true : propertyValue == null ? false : pattern.matcher(propertyValue).matches(); - } - - protected final List addonCandidates = Collections.synchronizedList(new ArrayList<>()); - private boolean connected = false; - - /** - * Implementation classes must implement their specific constructor and OSGI annotate it so that the OSGI framework - * will call it when the component is activated. Such methods must call this {@code activate()} method. Depending on - * the given configuration properties, it either connects (enables) or disconnects (disables) the service. - * - * @param configProperties the configuration properties. - */ - protected void activate(@Nullable Map configProperties) { - if (connected ^ getTargetConnected(configProperties)) { - if (connected) { - disconnect(); - } else - connect(); - } - } - - /** - * Implementation classes must override this method in order to connect (enable) the service. Implementations must - * call {@code super.connect()}. - */ - protected void connect() { - connected = true; - } - - /** - * Implementation classes must override this method and OSGI annotate it so that the OSGI framework will call it - * when the component is deactivated. Overridden methods must call {@code super.deactivate()}. Generally it should - * clear the finder state and disable the service. - */ - public void deactivate() { - if (connected) { - disconnect(); - } - addonCandidates.clear(); - } - - /** - * Implementation classes must override this method in order to disconnect (disable) the service. Implementations - * must call {@code super.disconnect()}. - */ - protected void disconnect() { - connected = false; - } - - /** - * Helper method that reads a configuration property and determines the target connected state. - * - * @param configProperties the configuration properties. - * @return true if the target is for the service to be connected, false otherwise. - */ - private boolean getTargetConnected(@Nullable Map configProperties) { - if (configProperties != null) { - Object property = configProperties.get(ADDON_SUGGESTION_FINDER_ENABLED_PROPERTY); - if (property instanceof String string) { - return Boolean.valueOf(string); - } else { - return !Boolean.FALSE.equals(property); - } - } - return true; - } - - /** - * Implementation classes must override this method and OSGI annotate it so that the OSGI framework will call it - * when the component configuration is modified. Overridden methods must call {@code super.modified()}. Generally - * it should have the same effect as the {@code activate()} method. - * - * @param configProperties the modified configuration properties. - */ - public void modified(@Nullable Map configProperties) { - activate(configProperties); - } - - public synchronized void setAddonCandidates(List candidates) { - addonCandidates.clear(); - addonCandidates.addAll(candidates); - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java deleted file mode 100644 index f1295a19b47..00000000000 --- a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionFinderServiceTests.java +++ /dev/null @@ -1,235 +0,0 @@ -/** - * 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.config.discovery.addon.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.jmdns.ServiceInfo; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.jupnp.UpnpService; -import org.jupnp.model.ValidationException; -import org.jupnp.model.meta.DeviceDetails; -import org.jupnp.model.meta.ManufacturerDetails; -import org.jupnp.model.meta.ModelDetails; -import org.jupnp.model.meta.RemoteDevice; -import org.jupnp.model.meta.RemoteDeviceIdentity; -import org.jupnp.model.meta.RemoteService; -import org.jupnp.model.types.DeviceType; -import org.jupnp.model.types.UDN; -import org.jupnp.registry.Registry; -import org.mockito.Mockito; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonMatchProperty; -import org.openhab.core.config.discovery.addon.AddonSuggestionFinderService; -import org.openhab.core.config.discovery.addon.finders.MDNSAddonSuggestionFinder; -import org.openhab.core.config.discovery.addon.finders.UpnpAddonSuggestionFinder; -import org.openhab.core.i18n.LocaleProvider; -import org.openhab.core.io.transport.mdns.MDNSClient; - -/** - * JUnit tests for the {@link AddonSuggestionFinderService}. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@TestInstance(Lifecycle.PER_CLASS) -public class AddonSuggestionFinderServiceTests { - - private @NonNullByDefault({}) LocaleProvider localeProvider; - private @NonNullByDefault({}) MDNSClient mdnsClient; - private @NonNullByDefault({}) UpnpService upnpService; - private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider; - private @NonNullByDefault({}) AddonSuggestionFinderService addonSuggestionFinderService; - - @AfterAll - public void cleanUp() { - assertNotNull(addonSuggestionFinderService); - try { - addonSuggestionFinderService.close(); - } catch (Exception e) { - fail(e); - } - } - - @BeforeAll - public void setup() { - setupMockLocaleProvider(); - setupMockAddonInfoProvider(); - setupMockMdnsClient(); - setupMockUpnpService(); - createAddonSuggestionFinderService(); - } - - private void createAddonSuggestionFinderService() { - addonSuggestionFinderService = new AddonSuggestionFinderService(localeProvider); - assertNotNull(addonSuggestionFinderService); - - UpnpAddonSuggestionFinder upnp = new UpnpAddonSuggestionFinder(null, upnpService); - Registry registry = upnpService.getRegistry(); - registry.getRemoteDevices().forEach(device -> upnp.remoteDeviceAdded(registry, device)); - addonSuggestionFinderService.addAddonSuggestionFinder(upnp); - - MDNSAddonSuggestionFinder mdns = new MDNSAddonSuggestionFinder(null, mdnsClient); - for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { - mdns.addService(service, true); - } - for (ServiceInfo service : mdnsClient.list("_printer._tcp.local.")) { - mdns.addService(service, true); - } - addonSuggestionFinderService.addAddonSuggestionFinder(mdns); - } - - private void setupMockAddonInfoProvider() { - AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(MDNSAddonSuggestionFinder.SERVICE_TYPE) - .setMatchProperties( - List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)"))) - .setMdnsServiceType("_printer._tcp.local."); - - AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(UpnpAddonSuggestionFinder.SERVICE_TYPE) - .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); - - AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(MDNSAddonSuggestionFinder.SERVICE_TYPE) - .setMdnsServiceType("_hue._tcp.local."); - - // create the mock - addonInfoProvider = mock(AddonInfoProvider.class); - Set addonInfos = new HashSet<>(); - addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") - .withDiscoveryMethods(List.of(hue1, hue2)).build()); - - addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer") - .withDiscoveryMethods(List.of(hp)).build()); - when(addonInfoProvider.getAddonInfos(any(Locale.class))).thenReturn(addonInfos); - - // check that it works - assertNotNull(addonInfoProvider); - Set addonInfos2 = addonInfoProvider.getAddonInfos(Locale.US); - assertEquals(2, addonInfos2.size()); - assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); - assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); - } - - private void setupMockLocaleProvider() { - // create the mock - localeProvider = mock(LocaleProvider.class); - when(localeProvider.getLocale()).thenReturn(Locale.US); - - // check that it works - assertNotNull(localeProvider); - assertEquals(Locale.US, localeProvider.getLocale()); - } - - private void setupMockMdnsClient() { - // create the mock - mdnsClient = mock(MDNSClient.class, Mockito.RETURNS_DEEP_STUBS); - when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {}); - ServiceInfo hueService = ServiceInfo.create("hue", "hue", 0, 0, 0, false, "hue service"); - when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService }); - ServiceInfo hpService = ServiceInfo.create("printer", "hpprinter", 0, 0, 0, false, "hp printer service"); - hpService.setText(Map.of("ty", "hp printer", "rp", "anything")); - when(mdnsClient.list(eq("_printer._tcp.local."))).thenReturn(new ServiceInfo[] { hpService }); - - // check that it works - assertNotNull(mdnsClient); - ServiceInfo[] result; - result = mdnsClient.list("_printer._tcp.local."); - assertEquals(1, result.length); - assertEquals("hpprinter", result[0].getName()); - assertEquals(2, Collections.list(result[0].getPropertyNames()).size()); - assertEquals("hp printer", result[0].getPropertyString("ty")); - result = mdnsClient.list("_hue._tcp.local."); - assertEquals(1, result.length); - assertEquals("hue", result[0].getName()); - result = mdnsClient.list("aardvark"); - assertEquals(0, result.length); - } - - private void setupMockUpnpService() { - // create the mock - upnpService = mock(UpnpService.class, Mockito.RETURNS_DEEP_STUBS); - URL url = null; - try { - url = new URL("http://www.openhab.org/"); - } catch (MalformedURLException e) { - fail("MalformedURLException"); - } - UDN udn = new UDN("udn"); - InetAddress address = null; - try { - address = InetAddress.getByName("127.0.0.1"); - } catch (UnknownHostException e) { - fail("UnknownHostException"); - } - RemoteDeviceIdentity identity = new RemoteDeviceIdentity(udn, 0, url, new byte[] {}, address); - DeviceType type = new DeviceType("nameSpace", "type"); - ManufacturerDetails manDetails = new ManufacturerDetails("manufacturer", "manufacturerURI"); - ModelDetails modDetails = new ModelDetails("Philips hue bridge", "modelDescription", "modelNumber", "modelURI"); - DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber", - "000123456789"); - List<@Nullable RemoteDevice> remoteDevices = new ArrayList<>(); - try { - remoteDevices.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null)); - } catch (ValidationException e1) { - fail("ValidationException"); - } - when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevices); - - // check that it works - assertNotNull(upnpService); - List result = new ArrayList<>(upnpService.getRegistry().getRemoteDevices()); - assertEquals(1, result.size()); - RemoteDevice device = result.get(0); - assertEquals("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer()); - assertEquals("serialNumber", device.getDetails().getSerialNumber()); - } - - @Test - public void testGetSuggestedAddons() { - addonSuggestionFinderService.addAddonInfoProvider(addonInfoProvider); - Set addons = addonSuggestionFinderService.getSuggestedAddons(localeProvider.getLocale()); - assertEquals(2, addons.size()); - assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID()))); - assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); - assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); - } -} diff --git a/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionServiceTests.java b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionServiceTests.java new file mode 100644 index 00000000000..0d72ac4d597 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon/src/test/java/org/openhab/core/config/discovery/addon/tests/AddonSuggestionServiceTests.java @@ -0,0 +1,196 @@ +/** + * 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.config.discovery.addon.tests; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.config.discovery.addon.AddonFinder; +import org.openhab.core.config.discovery.addon.AddonFinderConstants; +import org.openhab.core.config.discovery.addon.AddonSuggestionService; +import org.openhab.core.i18n.LocaleProvider; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; + +/** + * JUnit tests for the {@link AddonSuggestionService}. + * + * @author Andrew Fiddian-Green - Initial contribution + * @author Mark Herwege - Adapted to finders in separate packages + */ +@NonNullByDefault +@TestInstance(Lifecycle.PER_CLASS) +public class AddonSuggestionServiceTests { + + private @NonNullByDefault({}) ConfigurationAdmin configurationAdmin; + private @NonNullByDefault({}) LocaleProvider localeProvider; + private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider; + private @NonNullByDefault({}) AddonFinder mdnsAddonFinder; + private @NonNullByDefault({}) AddonFinder upnpAddonFinder; + private @NonNullByDefault({}) AddonSuggestionService addonSuggestionService; + + private final Map config = Map.of(AddonFinderConstants.CFG_FINDER_MDNS, true, + AddonFinderConstants.CFG_FINDER_UPNP, true); + + @AfterAll + public void cleanUp() { + assertNotNull(addonSuggestionService); + try { + addonSuggestionService.close(); + } catch (Exception e) { + fail(e); + } + } + + @BeforeAll + public void setup() { + setupMockConfigurationAdmin(); + setupMockLocaleProvider(); + setupMockAddonInfoProvider(); + setupMockMdnsAddonFinder(); + setupMockUpnpAddonFinder(); + addonSuggestionService = createAddonSuggestionService(); + } + + private AddonSuggestionService createAddonSuggestionService() { + AddonSuggestionService addonSuggestionService = new AddonSuggestionService(configurationAdmin, localeProvider, + config); + assertNotNull(addonSuggestionService); + + addonSuggestionService.addAddonFinder(mdnsAddonFinder); + addonSuggestionService.addAddonFinder(upnpAddonFinder); + + return addonSuggestionService; + } + + private void setupMockConfigurationAdmin() { + // create the mock + configurationAdmin = mock(ConfigurationAdmin.class); + Configuration configuration = mock(Configuration.class); + try { + when(configurationAdmin.getConfiguration(any())).thenReturn(configuration); + } catch (IOException e) { + } + when(configuration.getProperties()).thenReturn(null); + + // check that it works + assertNotNull(configurationAdmin); + try { + assertNull(configurationAdmin.getConfiguration(AddonSuggestionService.CONFIG_PID).getProperties()); + } catch (IOException e) { + } + } + + private void setupMockLocaleProvider() { + // create the mock + localeProvider = mock(LocaleProvider.class); + when(localeProvider.getLocale()).thenReturn(Locale.US); + + // check that it works + assertNotNull(localeProvider); + assertEquals(Locale.US, localeProvider.getLocale()); + } + + private void setupMockAddonInfoProvider() { + AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS) + .setMatchProperties( + List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)"))) + .setMdnsServiceType("_printer._tcp.local."); + + AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_UPNP) + .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); + + AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS) + .setMdnsServiceType("_hue._tcp.local."); + + // create the mock + addonInfoProvider = mock(AddonInfoProvider.class); + Set addonInfos = new HashSet<>(); + addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge") + .withDiscoveryMethods(List.of(hue1, hue2)).build()); + + addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer") + .withDiscoveryMethods(List.of(hp)).build()); + when(addonInfoProvider.getAddonInfos(any(Locale.class))).thenReturn(addonInfos); + + // check that it works + assertNotNull(addonInfoProvider); + Set addonInfos2 = addonInfoProvider.getAddonInfos(Locale.US); + assertEquals(2, addonInfos2.size()); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); + } + + private void setupMockMdnsAddonFinder() { + // create the mock + mdnsAddonFinder = mock(AddonFinder.class); + + Set addonInfos = addonInfoProvider.getAddonInfos(Locale.US).stream().filter( + c -> c.getDiscoveryMethods().stream().anyMatch(m -> SERVICE_TYPE_MDNS.equals(m.getServiceType()))) + .collect(Collectors.toSet()); + when(mdnsAddonFinder.getSuggestedAddons()).thenReturn(addonInfos); + + // check that it works + assertNotNull(mdnsAddonFinder); + Set addonInfos2 = mdnsAddonFinder.getSuggestedAddons(); + assertEquals(2, addonInfos2.size()); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); + } + + private void setupMockUpnpAddonFinder() { + // create the mock + upnpAddonFinder = mock(AddonFinder.class); + + Set addonInfos = addonInfoProvider.getAddonInfos(Locale.US).stream().filter( + c -> c.getDiscoveryMethods().stream().anyMatch(m -> SERVICE_TYPE_UPNP.equals(m.getServiceType()))) + .collect(Collectors.toSet()); + when(upnpAddonFinder.getSuggestedAddons()).thenReturn(addonInfos); + + // check that it works + assertNotNull(upnpAddonFinder); + Set addonInfos2 = upnpAddonFinder.getSuggestedAddons(); + assertEquals(1, addonInfos2.size()); + assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + } + + @Test + public void testGetSuggestedAddons() { + addonSuggestionService.addAddonInfoProvider(addonInfoProvider); + Set addons = addonSuggestionService.getSuggestedAddons(localeProvider.getLocale()); + assertEquals(2, addons.size()); + assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID()))); + assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID()))); + } +} diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 13ff530ea5a..fff95f13025 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -56,7 +56,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.config.discovery.addon.AddonSuggestionFinderService; +import org.openhab.core.config.discovery.addon.AddonSuggestionService; import org.openhab.core.events.Event; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.JSONResponse; @@ -121,7 +121,7 @@ public class AddonResource implements RESTResource { private final ConfigurationService configurationService; private final AddonInfoRegistry addonInfoRegistry; private final ConfigDescriptionRegistry configDescriptionRegistry; - private final AddonSuggestionFinderService addonSuggestionFinderService; + private final AddonSuggestionService addonSuggestionService; private @Context @NonNullByDefault({}) UriInfo uriInfo; @@ -130,13 +130,13 @@ public AddonResource(final @Reference EventPublisher eventPublisher, final @Refe final @Reference ConfigurationService configurationService, final @Reference AddonInfoRegistry addonInfoRegistry, final @Reference ConfigDescriptionRegistry configDescriptionRegistry, - final @Reference AddonSuggestionFinderService addonSuggestionFinderService) { + final @Reference AddonSuggestionService addonSuggestionService) { this.eventPublisher = eventPublisher; this.localeService = localeService; this.configurationService = configurationService; this.addonInfoRegistry = addonInfoRegistry; this.configDescriptionRegistry = configDescriptionRegistry; - this.addonSuggestionFinderService = addonSuggestionFinderService; + this.addonSuggestionService = addonSuggestionService; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) @@ -191,7 +191,7 @@ public Response getSuggestions( @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); - return Response.ok(new Stream2JSONInputStream(addonSuggestionFinderService.getSuggestedAddons(locale).stream())) + return Response.ok(new Stream2JSONInputStream(addonSuggestionService.getSuggestedAddons(locale).stream())) .build(); } diff --git a/bundles/org.openhab.core.karaf/.classpath b/bundles/org.openhab.core.karaf/.classpath index 9e55698cddc..cdefc0340f0 100644 --- a/bundles/org.openhab.core.karaf/.classpath +++ b/bundles/org.openhab.core.karaf/.classpath @@ -25,5 +25,6 @@ + diff --git a/bundles/org.openhab.core.karaf/pom.xml b/bundles/org.openhab.core.karaf/pom.xml index 97267744d35..aa2d40cd057 100644 --- a/bundles/org.openhab.core.karaf/pom.xml +++ b/bundles/org.openhab.core.karaf/pom.xml @@ -30,6 +30,11 @@ org.openhab.core.config.core ${project.version} + + org.openhab.core.bundles + org.openhab.core.config.discovery.addon + ${project.version} + org.apache.karaf.features org.apache.karaf.features.core diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java new file mode 100644 index 00000000000..bcac5997bf0 --- /dev/null +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java @@ -0,0 +1,73 @@ +/** + * 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.karaf.internal; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.karaf.features.FeaturesService; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.config.discovery.addon.AddonFinderService; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This service is an implementation of an openHAB {@link AddonSuggestionFinderService} using the Karaf features + * service. This service allows dynamic installation/removal of add-on suggestion finders. + * + * @author Mark Herwege - Initial contribution + */ +@Component(name = "org.openhab.core.karafaddonfinders", immediate = true) +@NonNullByDefault +public class KarafAddonFinderService implements AddonFinderService { + private final Logger logger = LoggerFactory.getLogger(KarafAddonFinderService.class); + + private final ScheduledExecutorService scheduler; + private final FeaturesService featuresService; + + @Activate + public KarafAddonFinderService(final @Reference FeaturesService featuresService) { + this.featuresService = featuresService; + scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("karaf-addonfinders")); + } + + @Override + public void install(String id) { + scheduler.execute(() -> { + try { + if (!featuresService.isInstalled(featuresService.getFeature(id))) { + featuresService.installFeature(id); + } + } catch (Exception e) { + logger.error("Failed to install add-on suggestion finder {}", id, e); + } + }); + } + + @Override + public void uninstall(String id) { + scheduler.execute(() -> { + try { + if (featuresService.isInstalled(featuresService.getFeature(id))) { + featuresService.uninstallFeature(id); + } + } catch (Exception e) { + logger.error("Failed to uninstall add-on suggestion finder {}", id, e); + } + }); + } +} diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml index d036ea139f2..12069a8e0dd 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml @@ -17,6 +17,16 @@ expected. Enabling this option will include these entries in the list of available add-ons. false + + + Use uPnP network scan to suggest add-ons. + true + + + + Use mDNS network scan to suggest add-ons. + true + diff --git a/bundles/pom.xml b/bundles/pom.xml index a6bfd0d30f8..ad744143289 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -31,6 +31,8 @@ org.openhab.core.config.core org.openhab.core.config.discovery org.openhab.core.config.discovery.addon + org.openhab.core.config.discovery.addon.mdns + org.openhab.core.config.discovery.addon.upnp org.openhab.core.config.discovery.mdns org.openhab.core.config.discovery.usbserial org.openhab.core.config.discovery.usbserial.linuxsysfs diff --git a/features/.project b/features/.project new file mode 100644 index 00000000000..4955163db7c --- /dev/null +++ b/features/.project @@ -0,0 +1,17 @@ + + + org.openhab.core.reactor.features + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/features/karaf/.project b/features/karaf/.project new file mode 100644 index 00000000000..ce44d25c7a8 --- /dev/null +++ b/features/karaf/.project @@ -0,0 +1,17 @@ + + + org.openhab.core.reactor.features.karaf + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/features/karaf/openhab-core/src/main/feature/feature.xml b/features/karaf/openhab-core/src/main/feature/feature.xml index 03edb0d66dd..2f919eb97a1 100644 --- a/features/karaf/openhab-core/src/main/feature/feature.xml +++ b/features/karaf/openhab-core/src/main/feature/feature.xml @@ -66,22 +66,29 @@ mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version} mvn:org.openhab.core.bundles/org.openhab.core.io.rest.sse/${project.version} - openhab-core-config-discovery-addon + openhab-core-config-discovery-addon openhab-core-base + mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon/${project.version} + + + openhab-core-base + openhab-core-config-discovery-addon + mvn:org.openhab.core.bundles/org.openhab.core.io.transport.mdns/${project.version} + mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.mdns/${project.version} openhab.tp;filter:="(feature=jmdns)" openhab.tp-jmdns + - mvn:org.openhab.core.bundles/org.openhab.core.io.transport.mdns/${project.version} - + + openhab-core-base + openhab-core-config-discovery-addon + mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.upnp/${project.version} openhab.tp;filter:="(feature=jupnp)" openhab.tp-jupnp - - mvn:org.openhab.core.bundles/org.openhab.core.addon/${project.version} - mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon/${project.version} From a9e15998ff9243528d01cf9745aa75126382bb65 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 3 Dec 2023 13:18:36 +0000 Subject: [PATCH 68/98] [addon-suggestion-finder] resolve merge conflict Signed-off-by: Andrew Fiddian-Green --- .../.classpath | 14 ++++++--- .../discovery/addon/mdns/MdnsAddonFinder.java | 31 ++++++++++--------- .../.classpath | 14 ++++++--- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath b/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath index 8084e956b45..d3d6b3c11b6 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/.classpath @@ -1,22 +1,28 @@ - + + + + + + + + - - + - + diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java index 90e3e3b2da1..eca61cc2b3a 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java @@ -12,7 +12,8 @@ */ package org.openhab.core.config.discovery.addon.mdns; -import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*; +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_MDNS; +import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_MDNS; import java.util.HashSet; import java.util.List; @@ -68,6 +69,20 @@ public MdnsAddonFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } + /** + * Adds the given mDNS service to the set of discovered services. + * + * @param device the mDNS service to be added. + */ + public void addService(ServiceInfo service, boolean isResolved) { + String qualifiedName = service.getQualifiedName(); + if (isResolved || !services.containsKey(qualifiedName)) { + if (services.put(qualifiedName, service) == null) { + logger.trace("Added service: {}", qualifiedName); + } + } + } + @Deactivate public void deactivate() { services.clear(); @@ -101,20 +116,6 @@ public void unsetAddonCandidates() { super.unsetAddonCandidates(); } - /** - * Adds the given mDNS service to the set of discovered services. - * - * @param device the mDNS service to be added. - */ - public void addService(ServiceInfo service, boolean isResolved) { - String qualifiedName = service.getQualifiedName(); - if (isResolved || !services.containsKey(qualifiedName)) { - if (services.put(qualifiedName, service) == null) { - logger.trace("Added service: {}", qualifiedName); - } - } - } - @Override public Set getSuggestedAddons() { Set result = new HashSet<>(); diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath b/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath index 8084e956b45..d3d6b3c11b6 100644 --- a/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/.classpath @@ -1,22 +1,28 @@ - + + + + + + + + - - + - + From 073d0033d00c5c756749d58b85db5f99600c7ad6 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Sun, 3 Dec 2023 13:45:37 +0000 Subject: [PATCH 69/98] [addon-suggestion-finder] fix SAT error Signed-off-by: Andrew Fiddian-Green --- .../upnp/tests/UpnpAddonFinderTests.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java index 2ea0296d810..8c5bd09a6e0 100644 --- a/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/src/test/java/org/openhab/core/config/discovery/addon/upnp/tests/UpnpAddonFinderTests.java @@ -1,3 +1,15 @@ +/** + * 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.config.discovery.addon.upnp.tests; /** @@ -12,9 +24,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.net.InetAddress; import java.net.MalformedURLException; From 7f10099ff51d63cd3e198d308e3fe338f8e9bf5c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:37:20 +0000 Subject: [PATCH 70/98] Update bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/mdns/MdnsAddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java index eca61cc2b3a..ef58ae76742 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java @@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory; /** - * This is a {@link MdnsAddonFinder} for finding suggested Addons via MDNS. + * This is a {@link MdnsAddonFinder} for finding suggested add-ons via mDNS. * * @author Andrew Fiddian-Green - Initial contribution * @author Mark Herwege - refactor to allow uninstall From b7d1c62f05d26d1adb7e91e91bf49c5719ecc858 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:38:05 +0000 Subject: [PATCH 71/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/config/discovery/addon/AddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java index 34af610a294..2ef84d87f8a 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java @@ -19,7 +19,7 @@ import org.openhab.core.addon.AddonInfo; /** - * This is a {@link AddonFinder} interface for classes that find Addons that are suggested to be installed. + * This is a {@link AddonFinder} interface for classes that find add-ons that are suggested to be installed. * * @author Andrew Fiddian-Green - Initial contribution */ From 393a4bf24df614923f525b434abe2fa3292e3913 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:38:35 +0000 Subject: [PATCH 72/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/config/discovery/addon/AddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java index 2ef84d87f8a..cb64ea51179 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java @@ -27,7 +27,7 @@ public interface AddonFinder { /** - * The OH framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of + * The framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of * those that it suggests to be installed. */ public Set getSuggestedAddons(); From a0c3450bf375af0e034ad3195c8624fafd001579 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:39:04 +0000 Subject: [PATCH 73/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../org/openhab/core/config/discovery/addon/AddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java index cb64ea51179..26eec778abd 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinder.java @@ -33,7 +33,7 @@ public interface AddonFinder { public Set getSuggestedAddons(); /** - * The OH framework calls this method to provide a list of {@link AddonInfo} elements which contain potential + * The framework calls this method to provide a list of {@link AddonInfo} elements which contain potential * candidates that this finder can iterate over in order to detect which ones to return via the * {@code getSuggestedAddons()} method. * From c6ec7c5468efdfd7fb59181c35a4d3d4bed2bcd1 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:39:29 +0000 Subject: [PATCH 74/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/AddonSuggestionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java index 243e0e777fa..6f3bdb79f92 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java @@ -49,7 +49,7 @@ import org.osgi.service.component.annotations.ReferencePolicyOption; /** - * This is a {@link AddonSuggestionService} which discovers suggested Addons for the user to install. + * This is a {@link AddonSuggestionService} which discovers suggested add-ons for the user to install. * * @author Andrew Fiddian-Green - Initial contribution * @author Mark Herwege - Install/remove finders From 7b5e913d3a78c0489561bdaaccacdba232837300 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:39:53 +0000 Subject: [PATCH 75/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/config/discovery/addon/BaseAddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java index 655108cc12b..cb62e5351ef 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/BaseAddonFinder.java @@ -21,7 +21,7 @@ import org.openhab.core.addon.AddonInfo; /** - * This is a {@link BaseAddonFinder} abstract class for finding suggested Addons. + * This is a {@link BaseAddonFinder} abstract class for finding suggested add-ons. * * @author Andrew Fiddian-Green - Initial contribution */ From 5d12a3fc2e6e75bc8d57101258e637bd52033f0c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 13:31:58 +0000 Subject: [PATCH 76/98] [addon-suggestion-finder] adopt reviwer suggestions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon.eclipse/pom.xml | 5 ----- .../discovery/addon/mdns/MdnsAddonFinder.java | 10 +++++----- .../addon/mdns/tests/MdnsAddonFinderTests.java | 6 +++--- bundles/org.openhab.core.karaf/.classpath | 1 - .../src/main/resources/OH-INF/config/addons.xml | 2 ++ features/.project | 17 ----------------- features/karaf/.project | 17 ----------------- 7 files changed, 10 insertions(+), 48 deletions(-) delete mode 100644 features/.project delete mode 100644 features/karaf/.project diff --git a/bundles/org.openhab.core.addon.eclipse/pom.xml b/bundles/org.openhab.core.addon.eclipse/pom.xml index 0a1d3707294..1d5c0f10f8f 100644 --- a/bundles/org.openhab.core.addon.eclipse/pom.xml +++ b/bundles/org.openhab.core.addon.eclipse/pom.xml @@ -25,11 +25,6 @@ org.openhab.core.addon ${project.version} - - org.openhab.core.bundles - org.openhab.core.config.discovery.addon - ${project.version} - diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java index ef58ae76742..af965fed1cf 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java @@ -44,14 +44,14 @@ import org.slf4j.LoggerFactory; /** - * This is a {@link MdnsAddonFinder} for finding suggested add-ons via mDNS. + * This is a {@link MDNSAddonFinder} for finding suggested add-ons via mDNS. * * @author Andrew Fiddian-Green - Initial contribution * @author Mark Herwege - refactor to allow uninstall */ @NonNullByDefault -@Component(service = AddonFinder.class, name = MdnsAddonFinder.SERVICE_NAME) -public class MdnsAddonFinder extends BaseAddonFinder implements ServiceListener { +@Component(service = AddonFinder.class, name = MDNSAddonFinder.SERVICE_NAME) +public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener { public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS; public static final String SERVICE_NAME = SERVICE_NAME_MDNS; @@ -59,13 +59,13 @@ public class MdnsAddonFinder extends BaseAddonFinder implements ServiceListener private static final String NAME = "name"; private static final String APPLICATION = "application"; - private final Logger logger = LoggerFactory.getLogger(MdnsAddonFinder.class); + private final Logger logger = LoggerFactory.getLogger(MDNSAddonFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Map services = new ConcurrentHashMap<>(); private MDNSClient mdnsClient; @Activate - public MdnsAddonFinder(@Reference MDNSClient mdnsClient) { + public MDNSAddonFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java index 0c5f344a3cb..d202dea4d68 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java @@ -36,7 +36,7 @@ import org.openhab.core.config.discovery.addon.AddonFinder; import org.openhab.core.config.discovery.addon.AddonFinderConstants; import org.openhab.core.config.discovery.addon.AddonSuggestionService; -import org.openhab.core.config.discovery.addon.mdns.MdnsAddonFinder; +import org.openhab.core.config.discovery.addon.mdns.MDNSAddonFinder; import org.openhab.core.io.transport.mdns.MDNSClient; /** @@ -47,7 +47,7 @@ */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -public class MdnsAddonFinderTests { +public class MDNSAddonFinderTests { private @NonNullByDefault({}) MDNSClient mdnsClient; private @NonNullByDefault({}) AddonFinder addonFinder; @@ -61,7 +61,7 @@ public void setup() { } private void createAddonFinder() { - MdnsAddonFinder mdnsAddonFinder = new MdnsAddonFinder(mdnsClient); + MDNSAddonFinder mdnsAddonFinder = new MDNSAddonFinder(mdnsClient); assertNotNull(mdnsAddonFinder); for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { diff --git a/bundles/org.openhab.core.karaf/.classpath b/bundles/org.openhab.core.karaf/.classpath index cdefc0340f0..9e55698cddc 100644 --- a/bundles/org.openhab.core.karaf/.classpath +++ b/bundles/org.openhab.core.karaf/.classpath @@ -25,6 +25,5 @@ - diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml index 12069a8e0dd..d282c50a404 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml @@ -18,11 +18,13 @@ false + true Use uPnP network scan to suggest add-ons. true + true Use mDNS network scan to suggest add-ons. true diff --git a/features/.project b/features/.project deleted file mode 100644 index 4955163db7c..00000000000 --- a/features/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - org.openhab.core.reactor.features - - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - - diff --git a/features/karaf/.project b/features/karaf/.project deleted file mode 100644 index ce44d25c7a8..00000000000 --- a/features/karaf/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - org.openhab.core.reactor.features.karaf - - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - - From 25c64e84a1060c50ad6982c51c12ce9c3234df8b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 14:06:43 +0000 Subject: [PATCH 77/98] [addon-suggestion-finder] fix git rename bug part 1 Signed-off-by: Andrew Fiddian-Green --- ...nsAddonFinder.java => MDNSAardvarkAddonFinder.java} | 10 +++++----- ...derTests.java => MDNSAardvarkAddonFinderTests.java} | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) rename bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/{MdnsAddonFinder.java => MDNSAardvarkAddonFinder.java} (94%) rename bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/{MdnsAddonFinderTests.java => MDNSAardvarkAddonFinderTests.java} (95%) diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java similarity index 94% rename from bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java rename to bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java index af965fed1cf..cc6055090f4 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MdnsAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java @@ -44,14 +44,14 @@ import org.slf4j.LoggerFactory; /** - * This is a {@link MDNSAddonFinder} for finding suggested add-ons via mDNS. + * This is a {@link MDNSAardvarkAddonFinder} for finding suggested add-ons via mDNS. * * @author Andrew Fiddian-Green - Initial contribution * @author Mark Herwege - refactor to allow uninstall */ @NonNullByDefault -@Component(service = AddonFinder.class, name = MDNSAddonFinder.SERVICE_NAME) -public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener { +@Component(service = AddonFinder.class, name = MDNSAardvarkAddonFinder.SERVICE_NAME) +public class MDNSAardvarkAddonFinder extends BaseAddonFinder implements ServiceListener { public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS; public static final String SERVICE_NAME = SERVICE_NAME_MDNS; @@ -59,13 +59,13 @@ public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener private static final String NAME = "name"; private static final String APPLICATION = "application"; - private final Logger logger = LoggerFactory.getLogger(MDNSAddonFinder.class); + private final Logger logger = LoggerFactory.getLogger(MDNSAardvarkAddonFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Map services = new ConcurrentHashMap<>(); private MDNSClient mdnsClient; @Activate - public MDNSAddonFinder(@Reference MDNSClient mdnsClient) { + public MDNSAardvarkAddonFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java similarity index 95% rename from bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java rename to bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java index d202dea4d68..d0444093eb5 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MdnsAddonFinderTests.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java @@ -36,7 +36,7 @@ import org.openhab.core.config.discovery.addon.AddonFinder; import org.openhab.core.config.discovery.addon.AddonFinderConstants; import org.openhab.core.config.discovery.addon.AddonSuggestionService; -import org.openhab.core.config.discovery.addon.mdns.MDNSAddonFinder; +import org.openhab.core.config.discovery.addon.mdns.MDNSAardvarkAddonFinder; import org.openhab.core.io.transport.mdns.MDNSClient; /** @@ -47,7 +47,7 @@ */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -public class MDNSAddonFinderTests { +public class MDNSAardvarkAddonFinderTests { private @NonNullByDefault({}) MDNSClient mdnsClient; private @NonNullByDefault({}) AddonFinder addonFinder; @@ -61,7 +61,7 @@ public void setup() { } private void createAddonFinder() { - MDNSAddonFinder mdnsAddonFinder = new MDNSAddonFinder(mdnsClient); + MDNSAardvarkAddonFinder mdnsAddonFinder = new MDNSAardvarkAddonFinder(mdnsClient); assertNotNull(mdnsAddonFinder); for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { From e9facf76fa776bee5b82223af981675f99300d4a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 14:08:40 +0000 Subject: [PATCH 78/98] [addon-suggestion-finder] fix git rename bug part 2 Signed-off-by: Andrew Fiddian-Green --- ...NSAardvarkAddonFinder.java => MDNSAddonFinder.java} | 10 +++++----- ...AddonFinderTests.java => MDNSAddonFinderTests.java} | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) rename bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/{MDNSAardvarkAddonFinder.java => MDNSAddonFinder.java} (94%) rename bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/{MDNSAardvarkAddonFinderTests.java => MDNSAddonFinderTests.java} (95%) diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java similarity index 94% rename from bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java rename to bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java index cc6055090f4..af965fed1cf 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAardvarkAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java @@ -44,14 +44,14 @@ import org.slf4j.LoggerFactory; /** - * This is a {@link MDNSAardvarkAddonFinder} for finding suggested add-ons via mDNS. + * This is a {@link MDNSAddonFinder} for finding suggested add-ons via mDNS. * * @author Andrew Fiddian-Green - Initial contribution * @author Mark Herwege - refactor to allow uninstall */ @NonNullByDefault -@Component(service = AddonFinder.class, name = MDNSAardvarkAddonFinder.SERVICE_NAME) -public class MDNSAardvarkAddonFinder extends BaseAddonFinder implements ServiceListener { +@Component(service = AddonFinder.class, name = MDNSAddonFinder.SERVICE_NAME) +public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener { public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS; public static final String SERVICE_NAME = SERVICE_NAME_MDNS; @@ -59,13 +59,13 @@ public class MDNSAardvarkAddonFinder extends BaseAddonFinder implements ServiceL private static final String NAME = "name"; private static final String APPLICATION = "application"; - private final Logger logger = LoggerFactory.getLogger(MDNSAardvarkAddonFinder.class); + private final Logger logger = LoggerFactory.getLogger(MDNSAddonFinder.class); private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME); private final Map services = new ConcurrentHashMap<>(); private MDNSClient mdnsClient; @Activate - public MDNSAardvarkAddonFinder(@Reference MDNSClient mdnsClient) { + public MDNSAddonFinder(@Reference MDNSClient mdnsClient) { this.mdnsClient = mdnsClient; } diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAddonFinderTests.java similarity index 95% rename from bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java rename to bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAddonFinderTests.java index d0444093eb5..d202dea4d68 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAardvarkAddonFinderTests.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/test/java/org/openhab/core/config/discovery/addon/mdns/tests/MDNSAddonFinderTests.java @@ -36,7 +36,7 @@ import org.openhab.core.config.discovery.addon.AddonFinder; import org.openhab.core.config.discovery.addon.AddonFinderConstants; import org.openhab.core.config.discovery.addon.AddonSuggestionService; -import org.openhab.core.config.discovery.addon.mdns.MDNSAardvarkAddonFinder; +import org.openhab.core.config.discovery.addon.mdns.MDNSAddonFinder; import org.openhab.core.io.transport.mdns.MDNSClient; /** @@ -47,7 +47,7 @@ */ @NonNullByDefault @TestInstance(Lifecycle.PER_CLASS) -public class MDNSAardvarkAddonFinderTests { +public class MDNSAddonFinderTests { private @NonNullByDefault({}) MDNSClient mdnsClient; private @NonNullByDefault({}) AddonFinder addonFinder; @@ -61,7 +61,7 @@ public void setup() { } private void createAddonFinder() { - MDNSAardvarkAddonFinder mdnsAddonFinder = new MDNSAardvarkAddonFinder(mdnsClient); + MDNSAddonFinder mdnsAddonFinder = new MDNSAddonFinder(mdnsClient); assertNotNull(mdnsAddonFinder); for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) { From 2ceb3a9a10a650a07a936b1211e86ac640954785 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 6 Nov 2023 16:39:22 +0000 Subject: [PATCH 79/98] [addoninfo] core extensions Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-1.0.0.xsd | 79 +++++++++++-------- .../internal/AddonsInfoProviderInstaller.java | 56 +++++++++++++ 2 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index c32e01a70e4..c4ef068ca81 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -7,15 +7,30 @@ - - - - - - - - - + + + + + + + + + + Comma-separated list of two-letter ISO country codes. + + + + + The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is <type>.<name> + + + + + + + + + Comma-separated list of two-letter ISO country codes. @@ -81,31 +96,31 @@ - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + + diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java new file mode 100644 index 00000000000..e68fcb7c363 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java @@ -0,0 +1,56 @@ +/** + * 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.addon.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.addon.Addon; +import org.openhab.core.addon.AddonService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +/** + * The {@link AddonsInfoProviderInstaller} component to install the special AddonsInfoProvider addon. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true, name = AddonsInfoProviderInstaller.SERVICE_NAME) +public class AddonsInfoProviderInstaller { + + public static final String SERVICE_NAME = "Installer for the AddonsInfoProvider special addon"; + + private static final String KARAF_ADDONS_SERVICE_ID = "karaf"; + private static final String ADDONS_INFO_PROVIDER_UID = "misc" + Addon.ADDON_SEPARATOR + "addonsinfoprovider"; + + private boolean addonInstalled; + + public AddonsInfoProviderInstaller() { + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addAddonService(AddonService addonService) { + if (!addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { + addonService.install(ADDONS_INFO_PROVIDER_UID); + addonInstalled = true; + } + } + + public void removeAddonService(AddonService addonService) { + if (addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { + addonService.uninstall(ADDONS_INFO_PROVIDER_UID); + addonInstalled = false; + } + } +} From bde4aceca2d91f6c6b58e44e65d9d09c56702671 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:30:45 +0000 Subject: [PATCH 80/98] [addoninfo] remove addon installer; add code to core Signed-off-by: Andrew Fiddian-Green --- .../KarafAddonsInfoProvider.java | 138 ++++++++++++++++++ .../internal/AddonsInfoProviderInstaller.java | 56 ------- 2 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java new file mode 100644 index 00000000000..eac06c23dc3 --- /dev/null +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java @@ -0,0 +1,138 @@ +/** + * 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.addon.infoproviders; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.OpenHAB; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfo; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.addon.AddonInfoListReader; +import org.openhab.core.addon.AddonInfoProvider; +import org.openhab.core.addon.AddonMatchProperty; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.converters.ConversionException; + +/** + * The {@link KarafAddonsInfoProvider} provides information from the addon.xml file of + * the addons that will are packaged in the openhab-addons .kar file. + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +@Component(service = AddonInfoProvider.class, name = KarafAddonsInfoProvider.SERVICE_NAME) +public class KarafAddonsInfoProvider implements AddonInfoProvider { + + public static final String SERVICE_NAME = "karaf-addons-info-provider"; + + private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; + + private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); + private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "addons.xml"; + private final Set addonInfos = new HashSet<>(); + + @Activate + public KarafAddonsInfoProvider() { + initialize(); + if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { + testAddonDeveloperRegexSyntax(); + } + } + + @Deactivate + public void deactivate() { + addonInfos.clear(); + } + + @Override + public @Nullable AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale) { + return addonInfos.stream().filter(a -> a.getUID().equals(uid)).findFirst().orElse(null); + } + + @Override + public Set getAddonInfos(@Nullable Locale locale) { + return addonInfos; + } + + private void initialize() { + String addonsXml; + try (InputStream stream = new FileInputStream(addonsXmlPathName)) { + addonsXml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + logger.warn("The 'addons.xml' file is missing"); + return; + } + if (addonsXml.isBlank()) { + logger.warn("The 'addons.xml' file is empty"); + return; + } + try { + AddonInfoList addonInfoList = new AddonInfoListReader().readFromXML(addonsXml); + addonInfos.addAll(addonInfoList.getAddons().stream().collect(Collectors.toSet())); + } catch (ConversionException e) { + logger.warn("The 'addons.xml' file has invalid content"); + return; + } catch (XStreamException e) { + logger.warn("The 'addons.xml' file cannot be deserialized"); + return; + } + } + + /* + * The openhab-addons Maven build process checks individual developer addon.xml contributions + * against the 'addon-1.0.0.xsd' schema, but it can't check the discovery-method match-property + * regex syntax. Invalid regexes do throw exceptions at run-time, but the log can't identify the + * culprit addon. Ideally we need to add syntax checks to the Maven build; and this test is an + * interim solution. + */ + private void testAddonDeveloperRegexSyntax() { + List patternErrors = new ArrayList<>(); + for (AddonInfo addonInfo : addonInfos) { + for (AddonDiscoveryMethod discoveryMethod : addonInfo.getDiscoveryMethods()) { + for (AddonMatchProperty matchProperty : discoveryMethod.getMatchProperties()) { + try { + matchProperty.getPattern(); + } catch (PatternSyntaxException e) { + patternErrors.add(String.format( + "Regex syntax error in org.openhab.%s.%s addon.xml => %s in \"%s\" position %d", + addonInfo.getType(), addonInfo.getId(), e.getDescription(), e.getPattern(), + e.getIndex())); + } + } + } + } + if (!patternErrors.isEmpty()) { + logger.warn("The 'addons.xml' file has errors\n\t{}", String.join("\n\t", patternErrors)); + } + } +} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java deleted file mode 100644 index e68fcb7c363..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/AddonsInfoProviderInstaller.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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.addon.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.Addon; -import org.openhab.core.addon.AddonService; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; - -/** - * The {@link AddonsInfoProviderInstaller} component to install the special AddonsInfoProvider addon. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(immediate = true, name = AddonsInfoProviderInstaller.SERVICE_NAME) -public class AddonsInfoProviderInstaller { - - public static final String SERVICE_NAME = "Installer for the AddonsInfoProvider special addon"; - - private static final String KARAF_ADDONS_SERVICE_ID = "karaf"; - private static final String ADDONS_INFO_PROVIDER_UID = "misc" + Addon.ADDON_SEPARATOR + "addonsinfoprovider"; - - private boolean addonInstalled; - - public AddonsInfoProviderInstaller() { - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonService(AddonService addonService) { - if (!addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { - addonService.install(ADDONS_INFO_PROVIDER_UID); - addonInstalled = true; - } - } - - public void removeAddonService(AddonService addonService) { - if (addonInstalled && KARAF_ADDONS_SERVICE_ID.equals(addonService.getId())) { - addonService.uninstall(ADDONS_INFO_PROVIDER_UID); - addonInstalled = false; - } - } -} From 7f6851b70d763d0667f7308ce804ce5659075019 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:56:50 +0000 Subject: [PATCH 81/98] [addoninfo] fix path name Signed-off-by: Andrew Fiddian-Green --- .../core/addon/infoproviders/KarafAddonsInfoProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java index eac06c23dc3..b1e9da6e55f 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java @@ -58,8 +58,9 @@ public class KarafAddonsInfoProvider implements AddonInfoProvider { private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); - private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "addons.xml"; private final Set addonInfos = new HashSet<>(); + private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "etc" + File.separator + + "addons.xml"; @Activate public KarafAddonsInfoProvider() { From 52520a5962071fff328f544bbf03b7966586407d Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 17:59:23 +0000 Subject: [PATCH 82/98] [addoninfo] xml schemas Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-1.0.0.xsd | 79 ++++++++----------- .../schema/addon-list-1.0.1.xsd | 24 ++++++ 2 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd diff --git a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd index c4ef068ca81..c32e01a70e4 100644 --- a/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-1.0.0.xsd @@ -7,30 +7,15 @@ - - - - - - - - - - Comma-separated list of two-letter ISO country codes. - - - - - The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is <type>.<name> - - - - - - - - - + + + + + + + + + Comma-separated list of two-letter ISO country codes. @@ -96,31 +81,31 @@ - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - + + + + + + diff --git a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd new file mode 100644 index 00000000000..45ad201e918 --- /dev/null +++ b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + From 6316c89fc9cd096574da83ff840d4e4177824642 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 12:43:23 +0000 Subject: [PATCH 83/98] [addoninfo] xml schemas (again) Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-list-1.0.1.xsd | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd diff --git a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd b/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd deleted file mode 100644 index 45ad201e918..00000000000 --- a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - From 0837bd1c0b6eba5eaa15279bf6ef4f6eaa611bee Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 13:43:58 +0000 Subject: [PATCH 84/98] [addoninfo] update AddonsInfoProvider Signed-off-by: Andrew Fiddian-Green --- .../KarafAddonsInfoProvider.java | 139 ------------------ 1 file changed, 139 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java deleted file mode 100644 index b1e9da6e55f..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/KarafAddonsInfoProvider.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 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.addon.infoproviders; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.OpenHAB; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.addon.AddonInfoListReader; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonMatchProperty; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.thoughtworks.xstream.XStreamException; -import com.thoughtworks.xstream.converters.ConversionException; - -/** - * The {@link KarafAddonsInfoProvider} provides information from the addon.xml file of - * the addons that will are packaged in the openhab-addons .kar file. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(service = AddonInfoProvider.class, name = KarafAddonsInfoProvider.SERVICE_NAME) -public class KarafAddonsInfoProvider implements AddonInfoProvider { - - public static final String SERVICE_NAME = "karaf-addons-info-provider"; - - private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; - - private final Logger logger = LoggerFactory.getLogger(KarafAddonsInfoProvider.class); - private final Set addonInfos = new HashSet<>(); - private final String addonsXmlPathName = OpenHAB.getUserDataFolder() + File.separator + "etc" + File.separator - + "addons.xml"; - - @Activate - public KarafAddonsInfoProvider() { - initialize(); - if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { - testAddonDeveloperRegexSyntax(); - } - } - - @Deactivate - public void deactivate() { - addonInfos.clear(); - } - - @Override - public @Nullable AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale) { - return addonInfos.stream().filter(a -> a.getUID().equals(uid)).findFirst().orElse(null); - } - - @Override - public Set getAddonInfos(@Nullable Locale locale) { - return addonInfos; - } - - private void initialize() { - String addonsXml; - try (InputStream stream = new FileInputStream(addonsXmlPathName)) { - addonsXml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - logger.warn("The 'addons.xml' file is missing"); - return; - } - if (addonsXml.isBlank()) { - logger.warn("The 'addons.xml' file is empty"); - return; - } - try { - AddonInfoList addonInfoList = new AddonInfoListReader().readFromXML(addonsXml); - addonInfos.addAll(addonInfoList.getAddons().stream().collect(Collectors.toSet())); - } catch (ConversionException e) { - logger.warn("The 'addons.xml' file has invalid content"); - return; - } catch (XStreamException e) { - logger.warn("The 'addons.xml' file cannot be deserialized"); - return; - } - } - - /* - * The openhab-addons Maven build process checks individual developer addon.xml contributions - * against the 'addon-1.0.0.xsd' schema, but it can't check the discovery-method match-property - * regex syntax. Invalid regexes do throw exceptions at run-time, but the log can't identify the - * culprit addon. Ideally we need to add syntax checks to the Maven build; and this test is an - * interim solution. - */ - private void testAddonDeveloperRegexSyntax() { - List patternErrors = new ArrayList<>(); - for (AddonInfo addonInfo : addonInfos) { - for (AddonDiscoveryMethod discoveryMethod : addonInfo.getDiscoveryMethods()) { - for (AddonMatchProperty matchProperty : discoveryMethod.getMatchProperties()) { - try { - matchProperty.getPattern(); - } catch (PatternSyntaxException e) { - patternErrors.add(String.format( - "Regex syntax error in org.openhab.%s.%s addon.xml => %s in \"%s\" position %d", - addonInfo.getType(), addonInfo.getId(), e.getDescription(), e.getPattern(), - e.getIndex())); - } - } - } - } - if (!patternErrors.isEmpty()) { - logger.warn("The 'addons.xml' file has errors\n\t{}", String.join("\n\t", patternErrors)); - } - } -} From 59c8197943fa6d7f640f12c6cf40818cb1843827 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 23 Nov 2023 15:31:31 +0000 Subject: [PATCH 85/98] [addoninfp] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/infoproviders/AddonsInfoProvider.java | 4 ++-- .../openhab/core/config/core/xml/util/XmlDocumentReader.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java index 9b21a1a2d2e..308fdc243bb 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java @@ -88,10 +88,10 @@ private void initialize() { Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { try { String xml = Files.readString(f.toPath()); - if (!xml.isBlank()) { + if (xml != null && !xml.isBlank()) { addonInfos.addAll(reader.readFromXML(xml).getAddons().stream().collect(Collectors.toSet())); } else { - logger.warn("File '{}' is empty", f.getName()); + logger.warn("File '{}' contents are null or empty", f.getName()); } } catch (IOException e) { logger.warn("File '{}' could not be read", f.getName()); diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java index a413fcd9930..5c2754c6f6f 100644 --- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java +++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/xml/util/XmlDocumentReader.java @@ -109,8 +109,6 @@ protected void configureSecurity(XStream xstream) { /** * Reads the XML document containing a specific XML tag from the specified xml string and converts it to the * according object. - *

- * This method returns {@code null} if the given URL is {@code null}. * * @param xml a string containing the XML document to be read. * @return the conversion result object (could be null). From 94ba4014ca4b47ddcddf476c06930b4b7ecb6bad Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:30:53 +0000 Subject: [PATCH 86/98] [addonInfo extensions] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 8 +- .../infoproviders/AddonsInfoProvider.java | 133 ------------------ .../xml}/AddonInfoListReader.java | 12 +- .../addon/test/AddonInfoListReaderTest.java | 2 +- .../org.openhab.core.config.core/.classpath | 12 +- 5 files changed, 11 insertions(+), 156 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java rename bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/{ => internal/xml}/AddonInfoListReader.java (91%) diff --git a/bundles/org.openhab.core.addon/.classpath b/bundles/org.openhab.core.addon/.classpath index 634118741ac..b5320e6c5b4 100644 --- a/bundles/org.openhab.core.addon/.classpath +++ b/bundles/org.openhab.core.addon/.classpath @@ -24,16 +24,16 @@ - + + + - + - - diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java deleted file mode 100644 index 308fdc243bb..00000000000 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * 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.addon.infoproviders; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.OpenHAB; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoListReader; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonMatchProperty; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.thoughtworks.xstream.XStreamException; -import com.thoughtworks.xstream.converters.ConversionException; - -/** - * The {@link AddonsInfoProvider} reads all {@code userdata/addons/*.xml} files, each of which - * should contain a list of addon.xml elements, and convert their combined contents into a list - * of {@link AddonInfo} objects can be accessed via the {@link AddonInfoProvider} interface. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@Component(service = AddonInfoProvider.class, name = AddonsInfoProvider.SERVICE_NAME) -public class AddonsInfoProvider implements AddonInfoProvider { - - public static final String SERVICE_NAME = "addons-info-provider"; - - private static final boolean TEST_ADDON_DEVELOPER_REGEX_SYNTAX = true; - - private final Logger logger = LoggerFactory.getLogger(AddonsInfoProvider.class); - private final String folder = OpenHAB.getUserDataFolder() + File.separator + "addons"; - private final Set addonInfos = new HashSet<>(); - - @Activate - public AddonsInfoProvider() { - initialize(); - if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { - testAddonDeveloperRegexSyntax(); - } - } - - @Deactivate - public void deactivate() { - addonInfos.clear(); - } - - @Override - public @Nullable AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale) { - return addonInfos.stream().filter(a -> a.getUID().equals(uid)).findFirst().orElse(null); - } - - @Override - public Set getAddonInfos(@Nullable Locale locale) { - return addonInfos; - } - - private void initialize() { - AddonInfoListReader reader = new AddonInfoListReader(); - Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { - try { - String xml = Files.readString(f.toPath()); - if (xml != null && !xml.isBlank()) { - addonInfos.addAll(reader.readFromXML(xml).getAddons().stream().collect(Collectors.toSet())); - } else { - logger.warn("File '{}' contents are null or empty", f.getName()); - } - } catch (IOException e) { - logger.warn("File '{}' could not be read", f.getName()); - } catch (ConversionException e) { - logger.warn("File '{}' has invalid content", f.getName()); - } catch (XStreamException e) { - logger.warn("File '{}' could not deserialized", f.getName()); - } - }); - } - - /* - * The openhab-addons Maven build process checks individual developer addon.xml contributions - * against the 'addon-1.0.0.xsd' schema, but it can't check the discovery-method match-property - * regex syntax. Invalid regexes do throw exceptions at run-time, but the log can't identify the - * culprit addon. Ideally we need to add syntax checks to the Maven build; and this test is an - * interim solution. - */ - private void testAddonDeveloperRegexSyntax() { - List patternErrors = new ArrayList<>(); - for (AddonInfo addonInfo : addonInfos) { - for (AddonDiscoveryMethod discoveryMethod : addonInfo.getDiscoveryMethods()) { - for (AddonMatchProperty matchProperty : discoveryMethod.getMatchProperties()) { - try { - matchProperty.getPattern(); - } catch (PatternSyntaxException e) { - patternErrors.add(String.format( - "Regex syntax error in org.openhab.%s.%s addon.xml => %s in \"%s\" position %d", - addonInfo.getType(), addonInfo.getId(), e.getDescription(), e.getPattern(), - e.getIndex())); - } - } - } - } - if (!patternErrors.isEmpty()) { - logger.warn("The following errors were found\n\t{}", String.join("\n\t", patternErrors)); - } - } -} diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java similarity index 91% rename from bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java rename to bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java index 393444579c1..a733e169492 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoListReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java @@ -10,16 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.addon; +package org.openhab.core.addon.internal.xml; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.addon.internal.xml.AddonDiscoveryMethodConverter; -import org.openhab.core.addon.internal.xml.AddonInfoConverter; -import org.openhab.core.addon.internal.xml.AddonInfoListConverter; -import org.openhab.core.addon.internal.xml.AddonInfoXmlResult; -import org.openhab.core.addon.internal.xml.AddonMatchPropertyConverter; +import org.openhab.core.addon.AddonDiscoveryMethod; +import org.openhab.core.addon.AddonInfoList; +import org.openhab.core.addon.AddonMatchProperty; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionParameter; import org.openhab.core.config.core.ConfigDescriptionParameterGroup; @@ -39,7 +37,7 @@ import com.thoughtworks.xstream.XStream; /** - * The {@link AddonInfoListReader} reads XML documents, which contain the {@code binding} XML tag, and converts them to + * The {@link AddonInfoListReader} reads XML documents, which contain the {@code addon} XML tag, and converts them to * a List of {@link AddonInfoXmlResult} objects. *

* This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document. diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java index bc20aaa6620..2f0f9407015 100644 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java @@ -24,8 +24,8 @@ import org.openhab.core.addon.AddonDiscoveryMethod; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.addon.AddonInfoListReader; import org.openhab.core.addon.AddonMatchProperty; +import org.openhab.core.addon.internal.xml.AddonInfoListReader; /** * JUnit tests for {@link AddonInfoListReader}. diff --git a/bundles/org.openhab.core.config.core/.classpath b/bundles/org.openhab.core.config.core/.classpath index ae05ef201a9..5a77c00dbc1 100644 --- a/bundles/org.openhab.core.config.core/.classpath +++ b/bundles/org.openhab.core.config.core/.classpath @@ -6,12 +6,7 @@ - - - - - - + @@ -36,10 +31,5 @@ - - - - - From a34a1567ca78e7099991c595c9071a1eab879dd8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 15:25:00 +0000 Subject: [PATCH 87/98] [addonInfo extensions] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../openhab/core/addon/AddonInfoRegistry.java | 2 +- .../addon/test/AddonInfoListReaderTest.java | 121 ----------- .../test/AddonInfoRegistryMergeTest.java | 203 ------------------ 3 files changed, 1 insertion(+), 325 deletions(-) delete mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java delete mode 100644 bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 7b4c1e21df0..338a3cbfad6 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java @@ -43,7 +43,7 @@ public class AddonInfoRegistry { private final Collection addonInfoProviders = new CopyOnWriteArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { + protected void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) { addonInfoProviders.add(addonInfoProvider); } diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java deleted file mode 100644 index 2f0f9407015..00000000000 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 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.addon.test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Test; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoList; -import org.openhab.core.addon.AddonMatchProperty; -import org.openhab.core.addon.internal.xml.AddonInfoListReader; - -/** - * JUnit tests for {@link AddonInfoListReader}. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -class AddonInfoListReaderTest { - - // @formatter:off - private final String testXml = - "" - + " " - + " automation" - + " Groovy Scripting" - + " This adds a Groovy script engine." - + " none" - + " " - + " " - + " mdns" - + " _printer._tcp.local." - + " " - + " " - + " rp" - + " .*" - + " " - + " " - + " ty" - + " hp (.*)" - + " " - + " " - + " " - + " " - + " upnp" - + " " - + " " - + " modelName" - + " Philips hue bridge" - + " " - + " " - + " " - + " " - + " " - + ""; - // @formatter:on - - @Test - void testAddonInfoListReader() { - AddonInfoList addons = null; - try { - AddonInfoListReader reader = new AddonInfoListReader(); - addons = reader.readFromXML(testXml); - } catch (Exception e) { - fail(e); - } - assertNotNull(addons); - List addonsInfos = addons.getAddons(); - assertEquals(1, addonsInfos.size()); - AddonInfo addon = addonsInfos.get(0); - assertNotNull(addon); - List discoveryMethods = addon.getDiscoveryMethods(); - assertNotNull(discoveryMethods); - assertEquals(2, discoveryMethods.size()); - - AddonDiscoveryMethod method = discoveryMethods.get(0); - assertNotNull(method); - assertEquals("mdns", method.getServiceType()); - assertEquals("_printer._tcp.local.", method.getMdnsServiceType()); - List matchProperties = method.getMatchProperties(); - assertNotNull(matchProperties); - assertEquals(2, matchProperties.size()); - AddonMatchProperty property = matchProperties.get(0); - assertNotNull(property); - assertEquals("rp", property.getName()); - assertEquals(".*", property.getRegex()); - assertTrue(property.getPattern().matcher("the cat sat on the mat").matches()); - - method = discoveryMethods.get(1); - assertNotNull(method); - assertEquals("upnp", method.getServiceType()); - assertEquals("", method.getMdnsServiceType()); - matchProperties = method.getMatchProperties(); - assertNotNull(matchProperties); - assertEquals(1, matchProperties.size()); - property = matchProperties.get(0); - assertNotNull(property); - assertEquals("modelName", property.getName()); - assertEquals("Philips hue bridge", property.getRegex()); - assertTrue(property.getPattern().matcher("Philips hue bridge").matches()); - } -} diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java deleted file mode 100644 index e7cb73cd6dd..00000000000 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * 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.addon.test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.openhab.core.addon.AddonDiscoveryMethod; -import org.openhab.core.addon.AddonInfo; -import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.addon.AddonInfoRegistry; -import org.openhab.core.addon.AddonMatchProperty; - -/** - * JUnit test for the {@link AddonInfoRegistry} merge function. - * - * @author Andrew Fiddian-Green - Initial contribution - */ -@NonNullByDefault -@TestInstance(Lifecycle.PER_CLASS) -class AddonInfoRegistryMergeTest { - - private @Nullable AddonInfoProvider addonInfoProvider0; - private @Nullable AddonInfoProvider addonInfoProvider1; - private @Nullable AddonInfoProvider addonInfoProvider2; - - @BeforeAll - void beforeAll() { - addonInfoProvider0 = createAddonInfoProvider0(); - addonInfoProvider1 = createAddonInfoProvider1(); - addonInfoProvider2 = createAddonInfoProvider2(); - } - - private AddonInfoProvider createAddonInfoProvider0() { - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero") - .withDescription("description-zero").build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - private AddonInfoProvider createAddonInfoProvider1() { - AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns") - .setMdnsServiceType("_hue._tcp.local."); - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one") - .withDescription("description-one").withCountries("GB,NL").withConnection("local") - .withDiscoveryMethods(List.of(discoveryMethod)).build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - private AddonInfoProvider createAddonInfoProvider2() { - AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("upnp") - .setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge"))); - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two") - .withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle") - .withServiceId("service-id").withConfigDescriptionURI("http://www.openhab.org") - .withDiscoveryMethods(List.of(discoveryMethod)).build(); - AddonInfoProvider provider = mock(AddonInfoProvider.class); - when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); - when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null); - when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo); - when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null); - return provider; - } - - /** - * Test fetching a single addon-info from the registry with no merging. - */ - @Test - void testGetOneAddonInfo() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertNull(addonInfo.getSourceBundle()); - assertNotEquals("local", addonInfo.getConnection()); - assertEquals(0, addonInfo.getCountries().size()); - assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("binding.hue", addonInfo.getServiceId()); - assertEquals(0, addonInfo.getDiscoveryMethods().size()); - } - - /** - * Test fetching two addon-info's from the registry with merging. - */ - @Test - void testMergeAddonInfos2() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - assertNotNull(addonInfoProvider1); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertNull(addonInfo.getSourceBundle()); - assertEquals("local", addonInfo.getConnection()); - assertEquals(2, addonInfo.getCountries().size()); - assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("binding.hue", addonInfo.getServiceId()); - assertEquals(1, addonInfo.getDiscoveryMethods().size()); - } - - /** - * Test fetching three addon-info's from the registry with full merging. - */ - @Test - void testMergeAddonInfos3() { - AddonInfoRegistry registry = new AddonInfoRegistry(); - assertNotNull(addonInfoProvider0); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0)); - assertNotNull(addonInfoProvider1); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1)); - assertNotNull(addonInfoProvider2); - registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider2)); - - AddonInfo addonInfo; - addonInfo = registry.getAddonInfo("aardvark", Locale.US); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("aardvark", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", null); - assertNull(addonInfo); - addonInfo = registry.getAddonInfo("binding-hue", Locale.US); - assertNotNull(addonInfo); - - assertEquals("hue", addonInfo.getId()); - assertEquals("binding", addonInfo.getType()); - assertEquals("binding-hue", addonInfo.getUID()); - assertTrue(addonInfo.getName().startsWith("name-")); - assertTrue(addonInfo.getDescription().startsWith("description-")); - assertEquals("source-bundle", addonInfo.getSourceBundle()); - assertEquals("local", addonInfo.getConnection()); - assertEquals(4, addonInfo.getCountries().size()); - assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); - assertEquals("service-id", addonInfo.getServiceId()); - assertEquals(2, addonInfo.getDiscoveryMethods().size()); - } -} From a0a9878d4c80609476b13eed77c5d89f4fa7bd06 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 6 Dec 2023 14:01:54 +0000 Subject: [PATCH 88/98] [addon-suggestion-finder] add notice Signed-off-by: Andrew Fiddian-Green --- .../NOTICE | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE b/bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE new file mode 100644 index 00000000000..6c17d0d8a45 --- /dev/null +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE @@ -0,0 +1,14 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-core + From 5ffd0c22b08e4054c41c4a04184a8f8b07e7db12 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 6 Dec 2023 22:37:32 +0000 Subject: [PATCH 89/98] [AddonInfoAddonsXmlProvider] fix issue #3901 Signed-off-by: Andrew Fiddian-Green --- .../internal/xml/AddonInfoAddonsXmlProvider.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java index aeae88583bf..8e9ff783d33 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java @@ -79,8 +79,18 @@ public Set getAddonInfos(@Nullable Locale locale) { } private void initialize() { + File fileFolder = new File(folder); + try { + if (!fileFolder.isDirectory()) { + logger.warn("Folder '{}' does not exist", folder); + return; + } + } catch (SecurityException e) { + logger.warn("Folder '{}' security exception", folder); + return; + } AddonInfoListReader reader = new AddonInfoListReader(); - Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { + Stream.of(fileFolder.listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { try { String xml = Files.readString(f.toPath()); if (xml != null && !xml.isBlank()) { @@ -94,6 +104,8 @@ private void initialize() { logger.warn("File '{}' has invalid content", f.getName()); } catch (XStreamException e) { logger.warn("File '{}' could not be deserialized", f.getName()); + } catch (SecurityException e) { + logger.warn("File '{}' security exception", f.getName()); } }); } From d57af011308c2db5ddfaacac086ccf9e69333e76 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:54:03 +0000 Subject: [PATCH 90/98] Revert "[AddonInfoAddonsXmlProvider] fix issue #3901" This reverts commit 5ffd0c22b08e4054c41c4a04184a8f8b07e7db12. --- .../internal/xml/AddonInfoAddonsXmlProvider.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java index 8e9ff783d33..aeae88583bf 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java @@ -79,18 +79,8 @@ public Set getAddonInfos(@Nullable Locale locale) { } private void initialize() { - File fileFolder = new File(folder); - try { - if (!fileFolder.isDirectory()) { - logger.warn("Folder '{}' does not exist", folder); - return; - } - } catch (SecurityException e) { - logger.warn("Folder '{}' security exception", folder); - return; - } AddonInfoListReader reader = new AddonInfoListReader(); - Stream.of(fileFolder.listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { + Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> { try { String xml = Files.readString(f.toPath()); if (xml != null && !xml.isBlank()) { @@ -104,8 +94,6 @@ private void initialize() { logger.warn("File '{}' has invalid content", f.getName()); } catch (XStreamException e) { logger.warn("File '{}' could not be deserialized", f.getName()); - } catch (SecurityException e) { - logger.warn("File '{}' security exception", f.getName()); } }); } From 34f5cd5c2b1b03daf115c115e1940fd79191fb0f Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:55:50 +0000 Subject: [PATCH 91/98] Update bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/mdns/MDNSAddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java index af965fed1cf..6af2d646663 100644 --- a/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.mdns/src/main/java/org/openhab/core/config/discovery/addon/mdns/MDNSAddonFinder.java @@ -139,7 +139,7 @@ && propertyMatches(matchProperties, APPLICATION, service.getApplication()) && matchPropertyKeys.stream().allMatch( name -> propertyMatches(matchProperties, name, service.getPropertyString(name)))) { result.add(candidate); - logger.debug("Suggested addon found: {}", candidate.getUID()); + logger.debug("Suggested add-on found: {}", candidate.getUID()); break; } } From ed6a0fef20b8ed1dc18604fa1accfb647095a28c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:56:03 +0000 Subject: [PATCH 92/98] Update bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/upnp/UpnpAddonFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java index 65905246fe2..034ac431c62 100644 --- a/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java +++ b/bundles/org.openhab.core.config.discovery.addon.upnp/src/main/java/org/openhab/core/config/discovery/addon/upnp/UpnpAddonFinder.java @@ -128,7 +128,7 @@ public Set getSuggestedAddons() { propertyNames.removeAll(SUPPORTED_PROPERTIES); if (!propertyNames.isEmpty()) { - logger.warn("Addon '{}' addon.xml file contains unsupported 'match-property' [{}]", + logger.warn("Add-on '{}' addon.xml file contains unsupported 'match-property' [{}]", candidate.getUID(), String.join(",", propertyNames)); break; } From 043322188eb153df676ee6f0d649fd3f40dbf634 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:56:20 +0000 Subject: [PATCH 93/98] Update bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/AddonSuggestionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java index 6f3bdb79f92..17a8d6a3778 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java @@ -81,7 +81,7 @@ public AddonSuggestionService(final @Reference ConfigurationAdmin configurationA modified(config); changed(); - // Changes to the configuration are expected to call the {@link Modified} method. This works well when running + // Changes to the configuration are expected to call the {@link modified} method. This works well when running // in Eclipse. Running in Karaf, the method was not consistently called. Therefore regularly check for changes // in configuration. // This pattern and code was re-used from {@link org.openhab.core.karaf.internal.FeatureInstaller} From 67996a2ecf5bad794d60da935088e4310e418b75 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:56:57 +0000 Subject: [PATCH 94/98] Update bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../src/main/resources/OH-INF/config/addons.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml index d282c50a404..aa999813b77 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml @@ -19,7 +19,7 @@ true - + Use uPnP network scan to suggest add-ons. true From 67e0a78a5878ebcc808a13e6283b5e63559dd374 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 11:57:11 +0000 Subject: [PATCH 95/98] Update bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml Co-authored-by: Kai Kreuzer Signed-off-by: Andrew Fiddian-Green --- .../src/main/resources/OH-INF/config/addons.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml index aa999813b77..2c143c39157 100644 --- a/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml +++ b/bundles/org.openhab.core/src/main/resources/OH-INF/config/addons.xml @@ -20,7 +20,7 @@ true - Use uPnP network scan to suggest add-ons. + Use UPnP network scan to suggest add-ons. true From da454f35feda1594201cf663bcc2103411850104 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 12:21:21 +0000 Subject: [PATCH 96/98] [addon-suggestion-finder] adopt (some of) reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../config/discovery/addon/AddonSuggestionService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java index 17a8d6a3778..bc53c00637d 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -35,7 +34,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.addon.AddonInfo; import org.openhab.core.addon.AddonInfoProvider; -import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.config.core.ConfigParser; import org.openhab.core.i18n.LocaleProvider; import org.osgi.service.cm.ConfigurationAdmin; @@ -85,7 +84,7 @@ public AddonSuggestionService(final @Reference ConfigurationAdmin configurationA // in Eclipse. Running in Karaf, the method was not consistently called. Therefore regularly check for changes // in configuration. // This pattern and code was re-used from {@link org.openhab.core.karaf.internal.FeatureInstaller} - scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("addon-suggestion-finder")); + scheduler = ThreadPoolManager.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); scheduler.scheduleWithFixedDelay(this::syncConfiguration, 1, 1, TimeUnit.MINUTES); } @@ -120,9 +119,9 @@ public void modified(@Nullable final Map config) { AddonFinderService finderService = addonFinderService; if (feature != null && finderService != null) { if (enabled) { - finderService.install(feature); + scheduler.execute(() -> finderService.install(feature)); } else { - finderService.uninstall(feature); + scheduler.execute(() -> finderService.uninstall(feature)); } } } From eb03f06a52daf7cf63b596f03e22676be7fca507 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 15:13:51 +0000 Subject: [PATCH 97/98] [addon-suggestion-finder] adapt to ongoing discussion Signed-off-by: Andrew Fiddian-Green --- .../addon/AddonSuggestionService.java | 9 ++--- .../internal/KarafAddonFinderService.java | 34 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java index bc53c00637d..5ee86cf1ee9 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonSuggestionService.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -67,8 +68,8 @@ public class AddonSuggestionService implements AutoCloseable { private @Nullable AddonFinderService addonFinderService; private @Nullable Map config; private final ScheduledExecutorService scheduler; - - private Map baseFinderConfig = new ConcurrentHashMap<>(); + private final Map baseFinderConfig = new ConcurrentHashMap<>(); + private final ScheduledFuture syncConfigurationTask; @Activate public AddonSuggestionService(final @Reference ConfigurationAdmin configurationAdmin, @@ -85,12 +86,12 @@ public AddonSuggestionService(final @Reference ConfigurationAdmin configurationA // in configuration. // This pattern and code was re-used from {@link org.openhab.core.karaf.internal.FeatureInstaller} scheduler = ThreadPoolManager.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON); - scheduler.scheduleWithFixedDelay(this::syncConfiguration, 1, 1, TimeUnit.MINUTES); + syncConfigurationTask = scheduler.scheduleWithFixedDelay(this::syncConfiguration, 1, 1, TimeUnit.MINUTES); } @Deactivate protected void deactivate() { - scheduler.shutdown(); + syncConfigurationTask.cancel(true); } @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java index bcac5997bf0..815d6eaed60 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonFinderService.java @@ -12,12 +12,8 @@ */ package org.openhab.core.karaf.internal; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - import org.apache.karaf.features.FeaturesService; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.common.NamedThreadFactory; import org.openhab.core.config.discovery.addon.AddonFinderService; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -36,38 +32,32 @@ public class KarafAddonFinderService implements AddonFinderService { private final Logger logger = LoggerFactory.getLogger(KarafAddonFinderService.class); - private final ScheduledExecutorService scheduler; private final FeaturesService featuresService; @Activate public KarafAddonFinderService(final @Reference FeaturesService featuresService) { this.featuresService = featuresService; - scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("karaf-addonfinders")); } @Override public void install(String id) { - scheduler.execute(() -> { - try { - if (!featuresService.isInstalled(featuresService.getFeature(id))) { - featuresService.installFeature(id); - } - } catch (Exception e) { - logger.error("Failed to install add-on suggestion finder {}", id, e); + try { + if (!featuresService.isInstalled(featuresService.getFeature(id))) { + featuresService.installFeature(id); } - }); + } catch (Exception e) { + logger.error("Failed to install add-on suggestion finder {}", id, e); + } } @Override public void uninstall(String id) { - scheduler.execute(() -> { - try { - if (featuresService.isInstalled(featuresService.getFeature(id))) { - featuresService.uninstallFeature(id); - } - } catch (Exception e) { - logger.error("Failed to uninstall add-on suggestion finder {}", id, e); + try { + if (featuresService.isInstalled(featuresService.getFeature(id))) { + featuresService.uninstallFeature(id); } - }); + } catch (Exception e) { + logger.error("Failed to uninstall add-on suggestion finder {}", id, e); + } } } From 59b099c04ac9a12717d69c485d265b3b85fe7d7c Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 7 Dec 2023 16:11:12 +0000 Subject: [PATCH 98/98] [addon-suggestion-finder] tweak java doc Signed-off-by: Andrew Fiddian-Green --- .../core/config/discovery/addon/AddonFinderService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java index 677899593b8..276e27fdc6e 100644 --- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java +++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderService.java @@ -26,8 +26,7 @@ public interface AddonFinderService { /** * Installs the given add-on suggestion finder. * - * This can be a long running process. The framework makes sure that this is called within a separate thread and - * add-on events will be sent upon its completion. + * This can be a long running process. The framework makes sure that this is called within a separate thread. * * @param id the id of the add-on suggestion finder to install */ @@ -36,8 +35,7 @@ public interface AddonFinderService { /** * Uninstalls the given add-on suggestion finder. * - * This can be a long running process. The framework makes sure that this is called within a separate thread and - * add-on events will be sent upon its completion. + * This can be a long running process. The framework makes sure that this is called within a separate thread. * * @param id the id of the add-on suggestion finder to uninstall */