From 236c8f2690604f584eab2831164317a6017b74dc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Mon, 6 Nov 2023 16:39:22 +0000 Subject: [PATCH 1/9] [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 ++++++++++++++++++ .../org.openhab.core.config.core/.classpath | 12 +- .../core/xml/util/XmlDocumentReader.java | 17 ++ .../core/addon/xml/test/AddonInfoTest.java | 33 ++- .../OH-INF/addon/addon.xml | 17 ++ 21 files changed, 1079 insertions(+), 27 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 21142133d17..0e265cad6c4 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 @@ - + + The id is used to construct the UID of this add-on to <type>-<name> @@ -80,4 +81,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 a33cdd09cd8..b5297e588b5 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/.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 ba3f30a4a55..449b11c12e2 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 dd09a15c9d4436576c2b72379d8fd0af99946eda Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:30:45 +0000 Subject: [PATCH 2/9] [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 418164d1b05a277071858e0c37ca27241b10a9cc Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 13:56:50 +0000 Subject: [PATCH 3/9] [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 ff2ddaa8d0facfd078354789498b15b91b3f88ef Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Wed, 8 Nov 2023 17:59:23 +0000 Subject: [PATCH 4/9] [addoninfo] xml schemas Signed-off-by: Andrew Fiddian-Green --- .../schema/addon-1.0.0.xsd | 102 +++++++++--------- .../schema/addon-list-1.0.1.xsd | 24 +++++ 2 files changed, 75 insertions(+), 51 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 0e265cad6c4..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,36 +7,36 @@ - - - - - - - - - - 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. + + + - The id is used to construct the UID of this add-on to <type>-<name> + 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> - - - + + + + + + + + + + The id is used to construct the UID of this add-on to <type>-<name> + + + @@ -81,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 c0d48dde2f36cd44b18d0670aded58961877412e Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 12:43:23 +0000 Subject: [PATCH 5/9] [addoninfo] xml schemas (again) Signed-off-by: Andrew Fiddian-Green --- ...addon-list-1.0.1.xsd => addon-info-list-1.0.0.xsd} | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) rename bundles/org.openhab.core.addon/schema/{addon-list-1.0.1.xsd => addon-info-list-1.0.0.xsd} (61%) diff --git a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd b/bundles/org.openhab.core.addon/schema/addon-info-list-1.0.0.xsd similarity index 61% rename from bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd rename to bundles/org.openhab.core.addon/schema/addon-info-list-1.0.0.xsd index 45ad201e918..4f720dc9815 100644 --- a/bundles/org.openhab.core.addon/schema/addon-list-1.0.1.xsd +++ b/bundles/org.openhab.core.addon/schema/addon-info-list-1.0.0.xsd @@ -1,16 +1,15 @@ + xmlns:addon-info-list="https://openhab.org/schemas/addon-info-list/v1.0.0" + targetNamespace="https://openhab.org/schemas/addon-info-list/v1.0.0"> - + - + - + From 720b581aa3748a64c156642f6e573a26774d4b6a Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 9 Nov 2023 13:43:58 +0000 Subject: [PATCH 6/9] [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 16845c5b6e29a3f881d4f902e0a28a28575d4bb8 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Thu, 23 Nov 2023 15:31:31 +0000 Subject: [PATCH 7/9] [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 449b11c12e2..4345a3130c7 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 164187c47eda8b2962380e2b4b930ff08d0e2c36 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 12:30:53 +0000 Subject: [PATCH 8/9] [addonInfo extensions] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- bundles/org.openhab.core.addon/.classpath | 8 +++--- .../xml/AddonInfoAddonsXmlProvider.java} | 25 ++++++++----------- .../xml}/AddonInfoListReader.java | 12 ++++----- .../addon/internal/xml/AddonInfoReader.java | 2 +- .../addon/test/AddonInfoListReaderTest.java | 2 +- .../org.openhab.core.config.core/.classpath | 12 +-------- 6 files changed, 22 insertions(+), 39 deletions(-) rename bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/{infoproviders/AddonsInfoProvider.java => internal/xml/AddonInfoAddonsXmlProvider.java} (83%) 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/internal/xml/AddonInfoAddonsXmlProvider.java similarity index 83% rename from bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/infoproviders/AddonsInfoProvider.java rename to bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoAddonsXmlProvider.java index 308fdc243bb..aeae88583bf 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/internal/xml/AddonInfoAddonsXmlProvider.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.addon.infoproviders; +package org.openhab.core.addon.internal.xml; import java.io.File; import java.io.IOException; @@ -29,7 +29,6 @@ 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; @@ -42,30 +41,26 @@ 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 + * 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 = AddonsInfoProvider.SERVICE_NAME) -public class AddonsInfoProvider implements AddonInfoProvider { +@Component(service = AddonInfoProvider.class, name = AddonInfoAddonsXmlProvider.SERVICE_NAME) +public class AddonInfoAddonsXmlProvider 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 Logger logger = LoggerFactory.getLogger(AddonInfoAddonsXmlProvider.class); private final String folder = OpenHAB.getUserDataFolder() + File.separator + "addons"; private final Set addonInfos = new HashSet<>(); @Activate - public AddonsInfoProvider() { + public AddonInfoAddonsXmlProvider() { initialize(); - if (TEST_ADDON_DEVELOPER_REGEX_SYNTAX) { - testAddonDeveloperRegexSyntax(); - } + testAddonDeveloperRegexSyntax(); } @Deactivate @@ -98,7 +93,7 @@ private void initialize() { } catch (ConversionException e) { logger.warn("File '{}' has invalid content", f.getName()); } catch (XStreamException e) { - logger.warn("File '{}' could not deserialized", f.getName()); + logger.warn("File '{}' could not be deserialized", f.getName()); } }); } @@ -127,7 +122,7 @@ private void testAddonDeveloperRegexSyntax() { } } if (!patternErrors.isEmpty()) { - logger.warn("The following errors were found\n\t{}", String.join("\n\t", patternErrors)); + 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/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 b5297e588b5..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 @@ -36,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. 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 d249146d8e3d664102a9ac7f29a6ee78c7e41081 Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 5 Dec 2023 15:25:00 +0000 Subject: [PATCH 9/9] [addonInfo extensions] adopt reviewer suggestions Signed-off-by: Andrew Fiddian-Green --- .../java/org/openhab/core/addon/AddonInfoRegistry.java | 2 +- .../core/addon/{test => }/AddonInfoListReaderTest.java | 6 +----- .../core/addon/{test => }/AddonInfoRegistryMergeTest.java | 7 +------ 3 files changed, 3 insertions(+), 12 deletions(-) rename bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/{test => }/AddonInfoListReaderTest.java (95%) rename bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/{test => }/AddonInfoRegistryMergeTest.java (97%) 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/AddonInfoListReaderTest.java similarity index 95% rename from bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoListReaderTest.java rename to bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java index 2f0f9407015..66c8485230f 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/AddonInfoListReaderTest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.addon.test; +package org.openhab.core.addon; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -21,10 +21,6 @@ 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; /** 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/AddonInfoRegistryMergeTest.java similarity index 97% rename from bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/test/AddonInfoRegistryMergeTest.java rename to bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java index e7cb73cd6dd..335dc1012ec 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/AddonInfoRegistryMergeTest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.addon.test; +package org.openhab.core.addon; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -33,11 +33,6 @@ 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.