diff --git a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java index 77c5d270b..daf13dd8c 100644 --- a/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java +++ b/bundles/org.openhab.core.addon.eclipse/src/main/java/org/openhab/core/addon/eclipse/internal/EclipseAddonService.java @@ -144,6 +144,7 @@ public class EclipseAddonService implements AddonService { if (addonInfo != null) { // only enrich if this add-on is installed, otherwise wrong data might be added addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription()) + .withKeywords(Objects.requireNonNullElse(addonInfo.getKeywords(), "")) .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withLink(getDefaultDocumentationLink(type, name)) .withConfigDescriptionURI(addonInfo.getConfigDescriptionURI()); diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java index 5e35a3807..62042bbfa 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/JsonAddonService.java @@ -209,7 +209,7 @@ public class JsonAddonService extends AbstractRemoteAddonService { .withDetailedDescription(addonEntry.description).withContentType(addonEntry.contentType) .withAuthor(addonEntry.author).withVersion(addonEntry.version).withLabel(addonEntry.title) .withCompatible(compatible).withMaturity(addonEntry.maturity).withProperties(properties) - .withLink(addonEntry.link).withImageLink(addonEntry.imageUrl) + .withLink(addonEntry.link).withImageLink(addonEntry.imageUrl).withKeywords(addonEntry.keywords) .withConfigDescriptionURI(addonEntry.configDescriptionURI).withLoggerPackages(addonEntry.loggerPackages) .withConnection(addonEntry.connection).withCountries(addonEntry.countries).build(); } diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/model/AddonEntryDTO.java b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/model/AddonEntryDTO.java index b91a135df..35f128061 100644 --- a/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/model/AddonEntryDTO.java +++ b/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/json/model/AddonEntryDTO.java @@ -26,6 +26,7 @@ public class AddonEntryDTO { public String id = ""; public String type = ""; public String description = ""; + public String keywords = ""; public String title = ""; public String link = ""; public String version = ""; 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 1420776fa..3411feefe 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 @@ -13,6 +13,11 @@ + + + Comma-separated list of key words for search. + + 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 7958d1a26..7399b3add 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 @@ -41,6 +41,7 @@ public class AddonInfo implements Identifiable { private final String uid; private final String name; private final String description; + private final @Nullable String keywords; private final @Nullable String connection; private final List countries; private final @Nullable String configDescriptionURI; @@ -49,8 +50,8 @@ public class AddonInfo implements Identifiable { private @Nullable List discoveryMethods; private AddonInfo(String id, String type, @Nullable String uid, String name, String description, - @Nullable String connection, List countries, @Nullable String configDescriptionURI, - @Nullable String serviceId, @Nullable String sourceBundle, + @Nullable String keywords, @Nullable String connection, List countries, + @Nullable String configDescriptionURI, @Nullable String serviceId, @Nullable String sourceBundle, @Nullable List discoveryMethods) throws IllegalArgumentException { // mandatory fields if (id.isBlank()) { @@ -73,6 +74,7 @@ public class AddonInfo implements Identifiable { this.description = description; // optional fields + this.keywords = keywords; this.connection = connection; this.countries = countries; this.configDescriptionURI = configDescriptionURI; @@ -127,6 +129,15 @@ public class AddonInfo implements Identifiable { return description; } + /** + * Returns a comma-separated list of keywords related to the add-on. e.g. "bluetooth". + * + * @return a comma-separated list of keywords, or null if no keywords string available + */ + public @Nullable String getKeywords() { + return keywords; + } + /** * Returns the link to a concrete {@link org.openhab.core.config.core.ConfigDescription}. * @@ -168,6 +179,7 @@ public class AddonInfo implements Identifiable { private @Nullable String uid; private String name = ""; private String description = ""; + private @Nullable String keywords; private @Nullable String connection; private List countries = List.of(); private @Nullable String configDescriptionURI = ""; @@ -186,6 +198,7 @@ public class AddonInfo implements Identifiable { this.uid = addonInfo.uid; this.name = addonInfo.name; this.description = addonInfo.description; + this.keywords = addonInfo.keywords; this.connection = addonInfo.connection; this.countries = addonInfo.countries; this.configDescriptionURI = addonInfo.configDescriptionURI; @@ -209,6 +222,11 @@ public class AddonInfo implements Identifiable { return this; } + public Builder withKeywords(@Nullable String keywords) { + this.keywords = keywords; + return this; + } + public Builder withConnection(@Nullable String connection) { this.connection = connection; return this; @@ -251,8 +269,8 @@ public class AddonInfo implements Identifiable { * @throws IllegalArgumentException if any of the information in this builder is invalid */ public AddonInfo build() throws IllegalArgumentException { - return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI, - serviceId, sourceBundle, discoveryMethods); + return new AddonInfo(id, type, uid, name, description, keywords, connection, countries, + configDescriptionURI, serviceId, sourceBundle, discoveryMethods); } } } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java index 6bdc24d27..2072b5765 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 @@ -12,6 +12,7 @@ */ package org.openhab.core.addon; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Locale; @@ -98,6 +99,16 @@ public class AddonInfoRegistry { if (a.getDescription().isBlank()) { builder.withDescription(b.getDescription()); } + Set keywords = new HashSet<>(); + if (a.getKeywords() instanceof String ka) { + Arrays.stream(ka.split(",")).map(String::trim).filter(s -> !s.isEmpty()).forEach(keywords::add); + } + if (b.getKeywords() instanceof String kb) { + Arrays.stream(kb.split(",")).map(String::trim).filter(s -> !s.isEmpty()).forEach(keywords::add); + } + if (!keywords.isEmpty()) { + builder.withKeywords(keywords.stream().collect(Collectors.joining(","))); + } if (a.getConnection() == null && b.getConnection() != null) { builder.withConnection(b.getConnection()); } diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java index 011653a89..b2759297c 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/JarFileAddonService.java @@ -157,6 +157,7 @@ public class JarFileAddonService extends BundleTracker implements AddonS .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withConfigDescriptionURI(addonInfo.getConfigDescriptionURI()) .withDescription(Objects.requireNonNullElse(addonInfo.getDescription(), bundle.getSymbolicName())) + .withKeywords(Objects.requireNonNullElse(addonInfo.getKeywords(), "")) .withContentType(ADDONS_CONTENT_TYPE).withLoggerPackages(List.of(bundle.getSymbolicName())).build(); } 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 1f3a54218..3a0989835 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 @@ -87,6 +87,8 @@ public class AddonInfoConverter extends GenericUnmarshaller "Add-on description is null or empty"); AddonInfo.Builder addonInfo = AddonInfo.builder(id, type).withName(name).withDescription(description); + + addonInfo.withKeywords((String) nodeIterator.nextValue("keywords", false)); addonInfo.withConnection((String) nodeIterator.nextValue("connection", false)); addonInfo.withCountries((String) nodeIterator.nextValue("countries", false)); addonInfo.withServiceId((String) nodeIterator.nextValue("service-id", false)); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java index db5babc72..79d551957 100644 --- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java +++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoListReader.java @@ -82,6 +82,7 @@ public class AddonInfoListReader extends XmlDocumentReader { xstream.alias("name", NodeValue.class); xstream.alias("description", NodeValue.class); xstream.alias("type", NodeValue.class); + xstream.alias("keywords", NodeValue.class); xstream.alias("connection", NodeValue.class); xstream.alias("countries", NodeValue.class); xstream.alias("config-description", ConfigDescription.class); diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/internal/xml/AddonInfoReader.java index 821581922..d65216da5 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 @@ -81,6 +81,7 @@ public class AddonInfoReader extends XmlDocumentReader { xstream.alias("name", NodeValue.class); xstream.alias("description", NodeValue.class); xstream.alias("type", NodeValue.class); + xstream.alias("keywords", NodeValue.class); xstream.alias("connection", NodeValue.class); xstream.alias("countries", NodeValue.class); xstream.alias("config-description", ConfigDescription.class); diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java index 9b1b4b9ed..1391da6ed 100644 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoListReaderTest.java @@ -37,6 +37,7 @@ class AddonInfoListReaderTest { + " automation" + " Groovy Scripting" + " This adds a Groovy script engine." + + " The,quick,brown,fox" + " none" + " " + " " @@ -86,6 +87,7 @@ class AddonInfoListReaderTest { assertEquals(1, addonsInfos.size()); AddonInfo addon = addonsInfos.getFirst(); assertNotNull(addon); + assertEquals("The,quick,brown,fox", addon.getKeywords()); List discoveryMethods = addon.getDiscoveryMethods(); assertNotNull(discoveryMethods); assertEquals(2, discoveryMethods.size()); diff --git a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java index 3d0d94ede..92347978e 100644 --- a/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java +++ b/bundles/org.openhab.core.addon/src/test/java/org/openhab/core/addon/AddonInfoRegistryMergeTest.java @@ -13,6 +13,7 @@ package org.openhab.core.addon; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -47,7 +48,7 @@ class AddonInfoRegistryMergeTest { } private AddonInfoProvider createAddonInfoProvider0() { - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero") + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero").withKeywords("the,quick") .withDescription("description-zero").build(); AddonInfoProvider provider = mock(AddonInfoProvider.class); when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null); @@ -60,7 +61,7 @@ class AddonInfoRegistryMergeTest { private AddonInfoProvider createAddonInfoProvider1() { AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns") .setParameters(List.of(new AddonParameter("mdnsServiceType", "_hue._tcp.local."))); - AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one") + AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one").withKeywords("brown,fox") .withDescription("description-one").withCountries("GB,NL").withConnection("local") .withDiscoveryMethods(List.of(discoveryMethod)).build(); AddonInfoProvider provider = mock(AddonInfoProvider.class); @@ -150,6 +151,11 @@ class AddonInfoRegistryMergeTest { assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI()); assertEquals("binding.hue", addonInfo.getServiceId()); assertEquals(1, addonInfo.getDiscoveryMethods().size()); + + assertTrue(addonInfo.getKeywords().contains("the")); + assertTrue(addonInfo.getKeywords().contains("quick")); + assertTrue(addonInfo.getKeywords().contains("brown")); + assertTrue(addonInfo.getKeywords().contains("fox")); } /** diff --git a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java index 800252812..a00eff2f8 100644 --- a/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java +++ b/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import org.apache.karaf.features.Feature; import org.apache.karaf.features.FeaturesService; @@ -135,6 +136,7 @@ public class KarafAddonService implements AddonService { if (addonInfo != null) { addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription()) + .withKeywords(Objects.requireNonNullElse(addonInfo.getKeywords(), "")) .withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries()) .withLink(getDefaultDocumentationLink(type, name)) .withConfigDescriptionURI(addonInfo.getConfigDescriptionURI());