addons) {
+ this.addons = addons;
+ return this;
+ }
+}
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..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
@@ -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;
@@ -44,34 +47,90 @@ protected 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/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/AddonInfoAddonsXmlProvider.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java
new file mode 100644
index 00000000000..aeae88583bf
--- /dev/null
+++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java
@@ -0,0 +1,128 @@
+/**
+ * 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.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.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 AddonInfoAddonsXmlProvider} reads all {@code userdata/addons/*.xml} files, each of which
+ * should contain a list of {@code addon} 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 = AddonInfoAddonsXmlProvider.SERVICE_NAME)
+public class AddonInfoAddonsXmlProvider implements AddonInfoProvider {
+
+ public static final String SERVICE_NAME = "addons-info-provider";
+
+ private final Logger logger = LoggerFactory.getLogger(AddonInfoAddonsXmlProvider.class);
+ private final String folder = OpenHAB.getUserDataFolder() + File.separator + "addons";
+ private final Set addonInfos = new HashSet<>();
+
+ @Activate
+ public AddonInfoAddonsXmlProvider() {
+ initialize();
+ 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 be 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/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/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java
new file mode 100644
index 00000000000..a733e169492
--- /dev/null
+++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java
@@ -0,0 +1,102 @@
+/**
+ * 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.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;
+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 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.
+ *
+ * @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/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java
index a33cdd09cd8..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;
@@ -33,7 +36,7 @@
import com.thoughtworks.xstream.XStream;
/**
- * The {@link AddonInfoReader} reads XML documents, which contain the {@code binding} XML tag,
+ * The {@link AddonInfoReader} reads XML documents, which contain the {@code addon} XML tag,
* and converts them to {@link AddonInfoXmlResult} objects.
*
* This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document.
@@ -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/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java
new file mode 100644
index 00000000000..66c8485230f
--- /dev/null
+++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java
@@ -0,0 +1,117 @@
+/**
+ * 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 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.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/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java
new file mode 100644
index 00000000000..335dc1012ec
--- /dev/null
+++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java
@@ -0,0 +1,198 @@
+/**
+ * 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 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;
+
+/**
+ * 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..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
@@ -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,18 @@ 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.
+ *
+ * @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
+
+
+
+
+