mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
Service to find suggested addons to install (#3806)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch> Co-authored-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
parent
de9912d06b
commit
62a50a409a
@ -304,6 +304,24 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon.mdns</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon.upnp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.core.bundles</groupId>
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
|
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>org.openhab.core.config.discovery.addon.mdns</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
14
bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE
Normal file
14
bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
This content is produced and maintained by the openHAB project.
|
||||||
|
|
||||||
|
* Project home: https://www.openhab.org
|
||||||
|
|
||||||
|
== Declared Project Licenses
|
||||||
|
|
||||||
|
This program and the accompanying materials are made available under the terms
|
||||||
|
of the Eclipse Public License 2.0 which is available at
|
||||||
|
https://www.eclipse.org/legal/epl-2.0/.
|
||||||
|
|
||||||
|
== Source Code
|
||||||
|
|
||||||
|
https://github.com/openhab/openhab-core
|
||||||
|
|
34
bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
Normal file
34
bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||||
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon.mdns</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Core :: Bundles :: mDNS Suggested Add-on Finder</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon.mdns;
|
||||||
|
|
||||||
|
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_MDNS;
|
||||||
|
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_MDNS;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.jmdns.ServiceEvent;
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import javax.jmdns.ServiceListener;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinder;
|
||||||
|
import org.openhab.core.config.discovery.addon.BaseAddonFinder;
|
||||||
|
import org.openhab.core.io.transport.mdns.MDNSClient;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Deactivate;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a {@link MDNSAddonFinder} for finding suggested add-ons via mDNS.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - refactor to allow uninstall
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(service = AddonFinder.class, name = MDNSAddonFinder.SERVICE_NAME)
|
||||||
|
public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener {
|
||||||
|
|
||||||
|
public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS;
|
||||||
|
public static final String SERVICE_NAME = SERVICE_NAME_MDNS;
|
||||||
|
|
||||||
|
private static final String NAME = "name";
|
||||||
|
private static final String APPLICATION = "application";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(MDNSAddonFinder.class);
|
||||||
|
private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME);
|
||||||
|
private final Map<String, ServiceInfo> services = new ConcurrentHashMap<>();
|
||||||
|
private MDNSClient mdnsClient;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public MDNSAddonFinder(@Reference MDNSClient mdnsClient) {
|
||||||
|
this.mdnsClient = mdnsClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given mDNS service to the set of discovered services.
|
||||||
|
*
|
||||||
|
* @param device the mDNS service to be added.
|
||||||
|
*/
|
||||||
|
public void addService(ServiceInfo service, boolean isResolved) {
|
||||||
|
String qualifiedName = service.getQualifiedName();
|
||||||
|
if (isResolved || !services.containsKey(qualifiedName)) {
|
||||||
|
if (services.put(qualifiedName, service) == null) {
|
||||||
|
logger.trace("Added service: {}", qualifiedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deactivate
|
||||||
|
public void deactivate() {
|
||||||
|
services.clear();
|
||||||
|
unsetAddonCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddonCandidates(List<AddonInfo> candidates) {
|
||||||
|
// Remove listeners for all service types that are no longer in candidates
|
||||||
|
addonCandidates.stream().filter(c -> !candidates.contains(c))
|
||||||
|
.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType()))
|
||||||
|
.filter(m -> !m.getMdnsServiceType().isEmpty())
|
||||||
|
.forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this)));
|
||||||
|
|
||||||
|
// Add listeners for all service types in candidates
|
||||||
|
super.setAddonCandidates(candidates);
|
||||||
|
addonCandidates
|
||||||
|
.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType()))
|
||||||
|
.filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> {
|
||||||
|
String serviceType = m.getMdnsServiceType();
|
||||||
|
mdnsClient.addServiceListener(serviceType, this);
|
||||||
|
scheduler.submit(() -> mdnsClient.list(serviceType));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unsetAddonCandidates() {
|
||||||
|
addonCandidates.forEach(c -> c.getDiscoveryMethods().stream()
|
||||||
|
.filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty())
|
||||||
|
.forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this)));
|
||||||
|
super.unsetAddonCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<AddonInfo> getSuggestedAddons() {
|
||||||
|
Set<AddonInfo> result = new HashSet<>();
|
||||||
|
for (AddonInfo candidate : addonCandidates) {
|
||||||
|
for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
|
||||||
|
.filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {
|
||||||
|
Map<String, Pattern> matchProperties = method.getMatchProperties().stream()
|
||||||
|
.collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern()));
|
||||||
|
|
||||||
|
Set<String> matchPropertyKeys = matchProperties.keySet().stream()
|
||||||
|
.filter(property -> (!NAME.equals(property) && !APPLICATION.equals(property)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
logger.trace("Checking candidate: {}", candidate.getUID());
|
||||||
|
for (ServiceInfo service : services.values()) {
|
||||||
|
|
||||||
|
logger.trace("Checking service: {}/{}", service.getQualifiedName(), service.getNiceTextString());
|
||||||
|
if (method.getMdnsServiceType().equals(service.getType())
|
||||||
|
&& propertyMatches(matchProperties, NAME, service.getName())
|
||||||
|
&& propertyMatches(matchProperties, APPLICATION, service.getApplication())
|
||||||
|
&& matchPropertyKeys.stream().allMatch(
|
||||||
|
name -> propertyMatches(matchProperties, name, service.getPropertyString(name)))) {
|
||||||
|
result.add(candidate);
|
||||||
|
logger.debug("Suggested add-on found: {}", candidate.getUID());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceName() {
|
||||||
|
return SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ************ MDNSClient call-back methods ************
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceAdded(@Nullable ServiceEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
ServiceInfo service = event.getInfo();
|
||||||
|
if (service != null) {
|
||||||
|
addService(service, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceRemoved(@Nullable ServiceEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceResolved(@Nullable ServiceEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
ServiceInfo service = event.getInfo();
|
||||||
|
if (service != null) {
|
||||||
|
addService(service, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon.mdns.tests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.addon.AddonMatchProperty;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinder;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinderConstants;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonSuggestionService;
|
||||||
|
import org.openhab.core.config.discovery.addon.mdns.MDNSAddonFinder;
|
||||||
|
import org.openhab.core.io.transport.mdns.MDNSClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit tests for the {@link AddonSuggestionService}.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - Adapted to finders in separate packages
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@TestInstance(Lifecycle.PER_CLASS)
|
||||||
|
public class MDNSAddonFinderTests {
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) MDNSClient mdnsClient;
|
||||||
|
private @NonNullByDefault({}) AddonFinder addonFinder;
|
||||||
|
private List<AddonInfo> addonInfos = new ArrayList<>();
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setup() {
|
||||||
|
setupMockMdnsClient();
|
||||||
|
setupAddonInfos();
|
||||||
|
createAddonFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAddonFinder() {
|
||||||
|
MDNSAddonFinder mdnsAddonFinder = new MDNSAddonFinder(mdnsClient);
|
||||||
|
assertNotNull(mdnsAddonFinder);
|
||||||
|
|
||||||
|
for (ServiceInfo service : mdnsClient.list("_hue._tcp.local.")) {
|
||||||
|
mdnsAddonFinder.addService(service, true);
|
||||||
|
}
|
||||||
|
for (ServiceInfo service : mdnsClient.list("_printer._tcp.local.")) {
|
||||||
|
mdnsAddonFinder.addService(service, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
addonFinder = mdnsAddonFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockMdnsClient() {
|
||||||
|
// create the mock
|
||||||
|
mdnsClient = mock(MDNSClient.class, Mockito.RETURNS_DEEP_STUBS);
|
||||||
|
when(mdnsClient.list(anyString())).thenReturn(new ServiceInfo[] {});
|
||||||
|
ServiceInfo hueService = ServiceInfo.create("hue", "hue", 0, 0, 0, false, "hue service");
|
||||||
|
when(mdnsClient.list(eq("_hue._tcp.local."))).thenReturn(new ServiceInfo[] { hueService });
|
||||||
|
ServiceInfo hpService = ServiceInfo.create("printer", "hpprinter", 0, 0, 0, false, "hp printer service");
|
||||||
|
hpService.setText(Map.of("ty", "hp printer", "rp", "anything"));
|
||||||
|
when(mdnsClient.list(eq("_printer._tcp.local."))).thenReturn(new ServiceInfo[] { hpService });
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(mdnsClient);
|
||||||
|
ServiceInfo[] result;
|
||||||
|
result = mdnsClient.list("_printer._tcp.local.");
|
||||||
|
assertEquals(1, result.length);
|
||||||
|
assertEquals("hpprinter", result[0].getName());
|
||||||
|
assertEquals(2, Collections.list(result[0].getPropertyNames()).size());
|
||||||
|
assertEquals("hp printer", result[0].getPropertyString("ty"));
|
||||||
|
result = mdnsClient.list("_hue._tcp.local.");
|
||||||
|
assertEquals(1, result.length);
|
||||||
|
assertEquals("hue", result[0].getName());
|
||||||
|
result = mdnsClient.list("aardvark");
|
||||||
|
assertEquals(0, result.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAddonInfos() {
|
||||||
|
AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS)
|
||||||
|
.setMatchProperties(
|
||||||
|
List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)")))
|
||||||
|
.setMdnsServiceType("_printer._tcp.local.");
|
||||||
|
addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer")
|
||||||
|
.withDiscoveryMethods(List.of(hp)).build());
|
||||||
|
|
||||||
|
AddonDiscoveryMethod hue = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS)
|
||||||
|
.setMdnsServiceType("_hue._tcp.local.");
|
||||||
|
addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge")
|
||||||
|
.withDiscoveryMethods(List.of(hue)).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSuggestedAddons() {
|
||||||
|
addonFinder.setAddonCandidates(addonInfos);
|
||||||
|
Set<AddonInfo> addons = addonFinder.getSuggestedAddons();
|
||||||
|
assertEquals(2, addons.size());
|
||||||
|
assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID())));
|
||||||
|
assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>org.openhab.core.config.discovery.addon.upnp</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
14
bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE
Normal file
14
bundles/org.openhab.core.config.discovery.addon.upnp/NOTICE
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
This content is produced and maintained by the openHAB project.
|
||||||
|
|
||||||
|
* Project home: https://www.openhab.org
|
||||||
|
|
||||||
|
== Declared Project Licenses
|
||||||
|
|
||||||
|
This program and the accompanying materials are made available under the terms
|
||||||
|
of the Eclipse Public License 2.0 which is available at
|
||||||
|
https://www.eclipse.org/legal/epl-2.0/.
|
||||||
|
|
||||||
|
== Source Code
|
||||||
|
|
||||||
|
https://github.com/openhab/openhab-core
|
||||||
|
|
29
bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml
Normal file
29
bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||||
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon.upnp</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Core :: Bundles :: uPnP Suggested Add-on Finder</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,247 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon.upnp;
|
||||||
|
|
||||||
|
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.jupnp.UpnpService;
|
||||||
|
import org.jupnp.model.meta.DeviceDetails;
|
||||||
|
import org.jupnp.model.meta.LocalDevice;
|
||||||
|
import org.jupnp.model.meta.ManufacturerDetails;
|
||||||
|
import org.jupnp.model.meta.ModelDetails;
|
||||||
|
import org.jupnp.model.meta.RemoteDevice;
|
||||||
|
import org.jupnp.model.meta.RemoteDeviceIdentity;
|
||||||
|
import org.jupnp.model.types.DeviceType;
|
||||||
|
import org.jupnp.model.types.UDN;
|
||||||
|
import org.jupnp.registry.Registry;
|
||||||
|
import org.jupnp.registry.RegistryListener;
|
||||||
|
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinder;
|
||||||
|
import org.openhab.core.config.discovery.addon.BaseAddonFinder;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Deactivate;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a {@link UpnpAddonFinder} for finding suggested Addons via UPnP.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - refactor to allow uninstall
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(service = AddonFinder.class, name = UpnpAddonFinder.SERVICE_NAME)
|
||||||
|
public class UpnpAddonFinder extends BaseAddonFinder implements RegistryListener {
|
||||||
|
|
||||||
|
public static final String SERVICE_TYPE = SERVICE_TYPE_UPNP;
|
||||||
|
public static final String SERVICE_NAME = SERVICE_NAME_UPNP;
|
||||||
|
|
||||||
|
private static final String DEVICE_TYPE = "deviceType";
|
||||||
|
private static final String MANUFACTURER = "manufacturer";
|
||||||
|
private static final String MANUFACTURER_URI = "manufacturerURI";
|
||||||
|
private static final String MODEL_NAME = "modelName";
|
||||||
|
private static final String MODEL_NUMBER = "modelNumber";
|
||||||
|
private static final String MODEL_DESCRIPTION = "modelDescription";
|
||||||
|
private static final String MODEL_URI = "modelURI";
|
||||||
|
private static final String SERIAL_NUMBER = "serialNumber";
|
||||||
|
private static final String FRIENDLY_NAME = "friendlyName";
|
||||||
|
|
||||||
|
private static final Set<String> SUPPORTED_PROPERTIES = Set.of(DEVICE_TYPE, MANUFACTURER, MANUFACTURER_URI,
|
||||||
|
MODEL_NAME, MODEL_NUMBER, MODEL_DESCRIPTION, MODEL_URI, SERIAL_NUMBER, FRIENDLY_NAME);
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(UpnpAddonFinder.class);
|
||||||
|
private final Map<String, RemoteDevice> devices = new ConcurrentHashMap<>();
|
||||||
|
private UpnpService upnpService;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public UpnpAddonFinder(@Reference UpnpService upnpService) {
|
||||||
|
this.upnpService = upnpService;
|
||||||
|
|
||||||
|
Registry registry = upnpService.getRegistry();
|
||||||
|
for (RemoteDevice device : registry.getRemoteDevices()) {
|
||||||
|
remoteDeviceAdded(registry, device);
|
||||||
|
}
|
||||||
|
registry.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deactivate
|
||||||
|
public void deactivate() {
|
||||||
|
unsetAddonCandidates();
|
||||||
|
|
||||||
|
UpnpService upnpService = this.upnpService;
|
||||||
|
upnpService.getRegistry().removeListener(this);
|
||||||
|
|
||||||
|
devices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given UPnP remote device to the set of discovered devices.
|
||||||
|
*
|
||||||
|
* @param device the UPnP remote device to be added.
|
||||||
|
*/
|
||||||
|
private void addDevice(RemoteDevice device) {
|
||||||
|
RemoteDeviceIdentity identity = device.getIdentity();
|
||||||
|
if (identity != null) {
|
||||||
|
UDN udn = identity.getUdn();
|
||||||
|
if (udn != null) {
|
||||||
|
String udnString = udn.getIdentifierString();
|
||||||
|
if (devices.put(udnString, device) == null) {
|
||||||
|
logger.trace("Added device: {}", device.getDisplayString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<AddonInfo> getSuggestedAddons() {
|
||||||
|
Set<AddonInfo> result = new HashSet<>();
|
||||||
|
for (AddonInfo candidate : addonCandidates) {
|
||||||
|
for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
|
||||||
|
.filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {
|
||||||
|
Map<String, Pattern> matchProperties = method.getMatchProperties().stream()
|
||||||
|
.collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern()));
|
||||||
|
|
||||||
|
Set<String> propertyNames = new HashSet<>(matchProperties.keySet());
|
||||||
|
propertyNames.removeAll(SUPPORTED_PROPERTIES);
|
||||||
|
|
||||||
|
if (!propertyNames.isEmpty()) {
|
||||||
|
logger.warn("Add-on '{}' addon.xml file contains unsupported 'match-property' [{}]",
|
||||||
|
candidate.getUID(), String.join(",", propertyNames));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Checking candidate: {}", candidate.getUID());
|
||||||
|
for (RemoteDevice device : devices.values()) {
|
||||||
|
|
||||||
|
String deviceType = null;
|
||||||
|
String serialNumber = null;
|
||||||
|
String friendlyName = null;
|
||||||
|
String manufacturer = null;
|
||||||
|
String manufacturerURI = null;
|
||||||
|
String modelName = null;
|
||||||
|
String modelNumber = null;
|
||||||
|
String modelDescription = null;
|
||||||
|
String modelURI = null;
|
||||||
|
|
||||||
|
DeviceType devType = device.getType();
|
||||||
|
if (devType != null) {
|
||||||
|
deviceType = devType.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceDetails devDetails = device.getDetails();
|
||||||
|
if (devDetails != null) {
|
||||||
|
friendlyName = devDetails.getFriendlyName();
|
||||||
|
serialNumber = devDetails.getSerialNumber();
|
||||||
|
|
||||||
|
ManufacturerDetails mfrDetails = devDetails.getManufacturerDetails();
|
||||||
|
if (mfrDetails != null) {
|
||||||
|
URI mfrUri = mfrDetails.getManufacturerURI();
|
||||||
|
manufacturer = mfrDetails.getManufacturer();
|
||||||
|
manufacturerURI = mfrUri != null ? mfrUri.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelDetails modDetails = devDetails.getModelDetails();
|
||||||
|
if (modDetails != null) {
|
||||||
|
URI modUri = modDetails.getModelURI();
|
||||||
|
modelName = modDetails.getModelName();
|
||||||
|
modelDescription = modDetails.getModelDescription();
|
||||||
|
modelNumber = modDetails.getModelNumber();
|
||||||
|
modelURI = modUri != null ? modUri.toString() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("Checking device: {}", device.getDisplayString());
|
||||||
|
if (propertyMatches(matchProperties, DEVICE_TYPE, deviceType)
|
||||||
|
&& propertyMatches(matchProperties, MANUFACTURER, manufacturer)
|
||||||
|
&& propertyMatches(matchProperties, MANUFACTURER_URI, manufacturerURI)
|
||||||
|
&& propertyMatches(matchProperties, MODEL_NAME, modelName)
|
||||||
|
&& propertyMatches(matchProperties, MODEL_NUMBER, modelNumber)
|
||||||
|
&& propertyMatches(matchProperties, MODEL_DESCRIPTION, modelDescription)
|
||||||
|
&& propertyMatches(matchProperties, MODEL_URI, modelURI)
|
||||||
|
&& propertyMatches(matchProperties, SERIAL_NUMBER, serialNumber)
|
||||||
|
&& propertyMatches(matchProperties, FRIENDLY_NAME, friendlyName)) {
|
||||||
|
result.add(candidate);
|
||||||
|
logger.debug("Suggested addon found: {}", candidate.getUID());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceName() {
|
||||||
|
return SERVICE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ************ UpnpService call-back methods ************
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeShutdown(@Nullable Registry registry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceAdded(@Nullable Registry registry, @Nullable LocalDevice localDevice) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceRemoved(@Nullable Registry registry, @Nullable LocalDevice localDevice) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceAdded(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) {
|
||||||
|
if (remoteDevice != null) {
|
||||||
|
addDevice(remoteDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryFailed(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice,
|
||||||
|
@Nullable Exception exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryStarted(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceRemoved(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceUpdated(@Nullable Registry registry, @Nullable RemoteDevice remoteDevice) {
|
||||||
|
if (remoteDevice != null) {
|
||||||
|
addDevice(remoteDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon.upnp.tests;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||||
|
import org.jupnp.UpnpService;
|
||||||
|
import org.jupnp.model.ValidationException;
|
||||||
|
import org.jupnp.model.meta.DeviceDetails;
|
||||||
|
import org.jupnp.model.meta.ManufacturerDetails;
|
||||||
|
import org.jupnp.model.meta.ModelDetails;
|
||||||
|
import org.jupnp.model.meta.RemoteDevice;
|
||||||
|
import org.jupnp.model.meta.RemoteDeviceIdentity;
|
||||||
|
import org.jupnp.model.meta.RemoteService;
|
||||||
|
import org.jupnp.model.types.DeviceType;
|
||||||
|
import org.jupnp.model.types.UDN;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.addon.AddonMatchProperty;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinder;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinderConstants;
|
||||||
|
import org.openhab.core.config.discovery.addon.upnp.UpnpAddonFinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit tests for the {@link UpnpAddonFinder}.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - Adapted to finders in separate packages
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@TestInstance(Lifecycle.PER_CLASS)
|
||||||
|
public class UpnpAddonFinderTests {
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) UpnpService upnpService;
|
||||||
|
private @NonNullByDefault({}) AddonFinder addonFinder;
|
||||||
|
private List<AddonInfo> addonInfos = new ArrayList<>();
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setup() {
|
||||||
|
setupMockUpnpService();
|
||||||
|
setupAddonInfos();
|
||||||
|
createAddonFinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAddonFinder() {
|
||||||
|
UpnpAddonFinder upnpAddonFinder = new UpnpAddonFinder(upnpService);
|
||||||
|
assertNotNull(upnpAddonFinder);
|
||||||
|
|
||||||
|
addonFinder = upnpAddonFinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockUpnpService() {
|
||||||
|
// create the mock
|
||||||
|
upnpService = mock(UpnpService.class, Mockito.RETURNS_DEEP_STUBS);
|
||||||
|
URL url = null;
|
||||||
|
try {
|
||||||
|
url = new URL("http://www.openhab.org/");
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
fail("MalformedURLException");
|
||||||
|
}
|
||||||
|
UDN udn = new UDN("udn");
|
||||||
|
InetAddress address = null;
|
||||||
|
try {
|
||||||
|
address = InetAddress.getByName("127.0.0.1");
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
fail("UnknownHostException");
|
||||||
|
}
|
||||||
|
RemoteDeviceIdentity identity = new RemoteDeviceIdentity(udn, 0, url, new byte[] {}, address);
|
||||||
|
DeviceType type = new DeviceType("nameSpace", "type");
|
||||||
|
ManufacturerDetails manDetails = new ManufacturerDetails("manufacturer", "manufacturerURI");
|
||||||
|
ModelDetails modDetails = new ModelDetails("Philips hue bridge", "modelDescription", "modelNumber", "modelURI");
|
||||||
|
DeviceDetails devDetails = new DeviceDetails("friendlyName", manDetails, modDetails, "serialNumber",
|
||||||
|
"000123456789");
|
||||||
|
List<@Nullable RemoteDevice> remoteDevices = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
remoteDevices.add(new RemoteDevice(identity, type, devDetails, (RemoteService) null));
|
||||||
|
} catch (ValidationException e1) {
|
||||||
|
fail("ValidationException");
|
||||||
|
}
|
||||||
|
when(upnpService.getRegistry().getRemoteDevices()).thenReturn(remoteDevices);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(upnpService);
|
||||||
|
List<RemoteDevice> result = new ArrayList<>(upnpService.getRegistry().getRemoteDevices());
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
RemoteDevice device = result.get(0);
|
||||||
|
assertEquals("manufacturer", device.getDetails().getManufacturerDetails().getManufacturer());
|
||||||
|
assertEquals("serialNumber", device.getDetails().getSerialNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAddonInfos() {
|
||||||
|
AddonDiscoveryMethod hue = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_UPNP)
|
||||||
|
.setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge")));
|
||||||
|
addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge")
|
||||||
|
.withDiscoveryMethods(List.of(hue)).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSuggestedAddons() {
|
||||||
|
addonFinder.setAddonCandidates(addonInfos);
|
||||||
|
Set<AddonInfo> addons = addonFinder.getSuggestedAddons();
|
||||||
|
assertEquals(1, addons.size());
|
||||||
|
assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID())));
|
||||||
|
assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
}
|
29
bundles/org.openhab.core.config.discovery.addon/.classpath
Normal file
29
bundles/org.openhab.core.config.discovery.addon/.classpath
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="annotationpath" value="target/dependency"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
23
bundles/org.openhab.core.config.discovery.addon/.project
Normal file
23
bundles/org.openhab.core.config.discovery.addon/.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>org.openhab.core.config.discovery.addon</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
14
bundles/org.openhab.core.config.discovery.addon/NOTICE
Normal file
14
bundles/org.openhab.core.config.discovery.addon/NOTICE
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
This content is produced and maintained by the openHAB project.
|
||||||
|
|
||||||
|
* Project home: https://www.openhab.org
|
||||||
|
|
||||||
|
== Declared Project Licenses
|
||||||
|
|
||||||
|
This program and the accompanying materials are made available under the terms
|
||||||
|
of the Eclipse Public License 2.0 which is available at
|
||||||
|
https://www.eclipse.org/legal/epl-2.0/.
|
||||||
|
|
||||||
|
== Source Code
|
||||||
|
|
||||||
|
https://github.com/openhab/openhab-core
|
||||||
|
|
24
bundles/org.openhab.core.config.discovery.addon/pom.xml
Normal file
24
bundles/org.openhab.core.config.discovery.addon/pom.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||||
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Core :: Bundles :: Add-on Suggestion Service</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a {@link AddonFinder} interface for classes that find add-ons that are suggested to be installed.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface AddonFinder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The framework calls this method to scan through the candidate list of {@link AddonInfo} and return a subset of
|
||||||
|
* those that it suggests to be installed.
|
||||||
|
*/
|
||||||
|
public Set<AddonInfo> getSuggestedAddons();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The framework calls this method to provide a list of {@link AddonInfo} elements which contain potential
|
||||||
|
* candidates that this finder can iterate over in order to detect which ones to return via the
|
||||||
|
* {@code getSuggestedAddons()} method.
|
||||||
|
*
|
||||||
|
* @param candidates a list of AddonInfo candidates.
|
||||||
|
*/
|
||||||
|
public void setAddonCandidates(List<AddonInfo> candidates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called from the framework to allow a finder to stop searching for addons and do cleanup.
|
||||||
|
*/
|
||||||
|
public void unsetAddonCandidates();
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This {@link AddonFinderConstants} contains constants describing addon finders available in core.
|
||||||
|
*
|
||||||
|
* @author Mark Herwege - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class AddonFinderConstants {
|
||||||
|
|
||||||
|
private static final String ADDON_SUGGESTION_FINDER = "-addon-suggestion-finder";
|
||||||
|
private static final String ADDON_SUGGESTION_FINDER_FEATURE = "openhab-core-config-discovery-addon-";
|
||||||
|
|
||||||
|
public static final String SERVICE_TYPE_MDNS = "mdns";
|
||||||
|
public static final String CFG_FINDER_MDNS = "suggestionFinderMdns";
|
||||||
|
public static final String SERVICE_NAME_MDNS = SERVICE_TYPE_MDNS + ADDON_SUGGESTION_FINDER;
|
||||||
|
public static final String FEATURE_MDNS = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_MDNS;
|
||||||
|
|
||||||
|
public static final String SERVICE_TYPE_UPNP = "upnp";
|
||||||
|
public static final String CFG_FINDER_UPNP = "suggestionFinderUpnp";
|
||||||
|
public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER;
|
||||||
|
public static final String FEATURE_UPNP = ADDON_SUGGESTION_FINDER_FEATURE + SERVICE_TYPE_UPNP;
|
||||||
|
|
||||||
|
public static final List<String> SUGGESTION_FINDERS = List.of(SERVICE_NAME_MDNS, SERVICE_NAME_UPNP);
|
||||||
|
public static final Map<String, String> SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_MDNS, CFG_FINDER_MDNS,
|
||||||
|
SERVICE_NAME_UPNP, CFG_FINDER_UPNP);
|
||||||
|
public static final Map<String, String> SUGGESTION_FINDER_FEATURES = Map.of(SERVICE_NAME_MDNS, FEATURE_MDNS,
|
||||||
|
SERVICE_NAME_UPNP, FEATURE_UPNP);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes implementing this interface can be registered as an OSGi service in order to provide functionality for
|
||||||
|
* managing add-on suggestion finders, such as installing and uninstalling them.
|
||||||
|
*
|
||||||
|
* @author Mark Herwege - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface AddonFinderService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the given add-on suggestion finder.
|
||||||
|
*
|
||||||
|
* This can be a long running process. The framework makes sure that this is called within a separate thread.
|
||||||
|
*
|
||||||
|
* @param id the id of the add-on suggestion finder to install
|
||||||
|
*/
|
||||||
|
void install(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the given add-on suggestion finder.
|
||||||
|
*
|
||||||
|
* This can be a long running process. The framework makes sure that this is called within a separate thread.
|
||||||
|
*
|
||||||
|
* @param id the id of the add-on suggestion finder to uninstall
|
||||||
|
*/
|
||||||
|
void uninstall(String id);
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon;
|
||||||
|
|
||||||
|
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Dictionary;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.addon.AddonInfoProvider;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
|
import org.openhab.core.config.core.ConfigParser;
|
||||||
|
import org.openhab.core.i18n.LocaleProvider;
|
||||||
|
import org.osgi.service.cm.ConfigurationAdmin;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Deactivate;
|
||||||
|
import org.osgi.service.component.annotations.Modified;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||||
|
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||||
|
import org.osgi.service.component.annotations.ReferencePolicyOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a {@link AddonSuggestionService} which discovers suggested add-ons for the user to install.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - Install/remove finders
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(immediate = true, service = AddonSuggestionService.class, name = AddonSuggestionService.SERVICE_NAME, configurationPid = AddonSuggestionService.CONFIG_PID)
|
||||||
|
public class AddonSuggestionService implements AutoCloseable {
|
||||||
|
|
||||||
|
public static final String SERVICE_NAME = "addon-suggestion-service";
|
||||||
|
public static final String CONFIG_PID = "org.openhab.addons";
|
||||||
|
|
||||||
|
private final Set<AddonInfoProvider> addonInfoProviders = ConcurrentHashMap.newKeySet();
|
||||||
|
private final List<AddonFinder> addonFinders = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
private final ConfigurationAdmin configurationAdmin;
|
||||||
|
private final LocaleProvider localeProvider;
|
||||||
|
private @Nullable AddonFinderService addonFinderService;
|
||||||
|
private @Nullable Map<String, Object> config;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
|
private final Map<String, Boolean> baseFinderConfig = new ConcurrentHashMap<>();
|
||||||
|
private final ScheduledFuture<?> syncConfigurationTask;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public AddonSuggestionService(final @Reference ConfigurationAdmin configurationAdmin,
|
||||||
|
@Reference LocaleProvider localeProvider, @Nullable Map<String, Object> config) {
|
||||||
|
this.configurationAdmin = configurationAdmin;
|
||||||
|
this.localeProvider = localeProvider;
|
||||||
|
|
||||||
|
SUGGESTION_FINDERS.forEach(f -> baseFinderConfig.put(f, true));
|
||||||
|
modified(config);
|
||||||
|
changed();
|
||||||
|
|
||||||
|
// Changes to the configuration are expected to call the {@link modified} method. This works well when running
|
||||||
|
// in Eclipse. Running in Karaf, the method was not consistently called. Therefore regularly check for changes
|
||||||
|
// in configuration.
|
||||||
|
// This pattern and code was re-used from {@link org.openhab.core.karaf.internal.FeatureInstaller}
|
||||||
|
scheduler = ThreadPoolManager.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||||
|
syncConfigurationTask = scheduler.scheduleWithFixedDelay(this::syncConfiguration, 1, 1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deactivate
|
||||||
|
protected void deactivate() {
|
||||||
|
syncConfigurationTask.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
|
||||||
|
protected void addAddonFinderService(AddonFinderService addonFinderService) {
|
||||||
|
this.addonFinderService = addonFinderService;
|
||||||
|
modified(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeAddonFinderService(AddonFinderService addonFinderService) {
|
||||||
|
AddonFinderService finderService = this.addonFinderService;
|
||||||
|
if ((finderService != null) && addonFinderService.getClass().isAssignableFrom(finderService.getClass())) {
|
||||||
|
this.addonFinderService = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Modified
|
||||||
|
public void modified(@Nullable final Map<String, Object> config) {
|
||||||
|
baseFinderConfig.forEach((finder, cfg) -> {
|
||||||
|
String cfgParam = SUGGESTION_FINDER_CONFIGS.get(finder);
|
||||||
|
if (cfgParam != null) {
|
||||||
|
boolean enabled = (config != null)
|
||||||
|
? ConfigParser.valueAsOrElse(config.get(cfgParam), Boolean.class, cfg)
|
||||||
|
: cfg;
|
||||||
|
baseFinderConfig.put(finder, enabled);
|
||||||
|
String feature = SUGGESTION_FINDER_FEATURES.get(finder);
|
||||||
|
AddonFinderService finderService = addonFinderService;
|
||||||
|
if (feature != null && finderService != null) {
|
||||||
|
if (enabled) {
|
||||||
|
scheduler.execute(() -> finderService.install(feature));
|
||||||
|
} else {
|
||||||
|
scheduler.execute(() -> finderService.uninstall(feature));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncConfiguration() {
|
||||||
|
try {
|
||||||
|
Dictionary<String, Object> cfg = configurationAdmin.getConfiguration(CONFIG_PID).getProperties();
|
||||||
|
if (cfg == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Map<String, Object> cfgMap = new HashMap<>();
|
||||||
|
final Enumeration<String> enumeration = cfg.keys();
|
||||||
|
while (enumeration.hasMoreElements()) {
|
||||||
|
final String key = enumeration.nextElement();
|
||||||
|
cfgMap.put(key, cfg.get(key));
|
||||||
|
}
|
||||||
|
if (!cfgMap.equals(config)) {
|
||||||
|
modified(cfgMap);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFinderEnabled(AddonFinder finder) {
|
||||||
|
if (finder instanceof BaseAddonFinder baseFinder) {
|
||||||
|
return baseFinderConfig.getOrDefault(baseFinder.getServiceName(), true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||||
|
public void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
|
||||||
|
addonInfoProviders.add(addonInfoProvider);
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
|
||||||
|
if (addonInfoProviders.remove(addonInfoProvider)) {
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||||
|
public void addAddonFinder(AddonFinder addonFinder) {
|
||||||
|
addonFinders.add(addonFinder);
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAddonFinder(AddonFinder addonFinder) {
|
||||||
|
if (addonFinders.remove(addonFinder)) {
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changed() {
|
||||||
|
List<AddonInfo> candidates = addonInfoProviders.stream().map(p -> p.getAddonInfos(localeProvider.getLocale()))
|
||||||
|
.flatMap(Collection::stream).toList();
|
||||||
|
addonFinders.stream().filter(this::isFinderEnabled).forEach(f -> f.setAddonCandidates(candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deactivate
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
addonFinders.clear();
|
||||||
|
addonInfoProviders.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<AddonInfo> getSuggestedAddons(@Nullable Locale locale) {
|
||||||
|
return addonFinders.stream().filter(this::isFinderEnabled).map(f -> f.getSuggestedAddons())
|
||||||
|
.flatMap(Collection::stream).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a {@link BaseAddonFinder} abstract class for finding suggested add-ons.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class BaseAddonFinder implements AddonFinder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to check if the given {@code propertyName} is in the {@code propertyPatternMap} and if so, the
|
||||||
|
* given {@code propertyValue} matches the respective regular expression {@code Pattern}.
|
||||||
|
*
|
||||||
|
* @param propertyPatternMap map of property names and regex patterns for value matching
|
||||||
|
* @param propertyName
|
||||||
|
* @param propertyValue
|
||||||
|
* @return true a) if the property name exists and the property value is not null and matches the regular
|
||||||
|
* expression, or b) the property name does not exist.
|
||||||
|
*/
|
||||||
|
protected static boolean propertyMatches(Map<String, Pattern> propertyPatternMap, String propertyName,
|
||||||
|
@Nullable String propertyValue) {
|
||||||
|
Pattern pattern = propertyPatternMap.get(propertyName);
|
||||||
|
return pattern == null ? true : propertyValue == null ? false : pattern.matcher(propertyValue).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected volatile List<AddonInfo> addonCandidates = List.of();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddonCandidates(List<AddonInfo> candidates) {
|
||||||
|
addonCandidates = candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unsetAddonCandidates() {
|
||||||
|
addonCandidates = List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getServiceName();
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.config.discovery.addon.tests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||||
|
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||||
|
import org.openhab.core.addon.AddonInfo;
|
||||||
|
import org.openhab.core.addon.AddonInfoProvider;
|
||||||
|
import org.openhab.core.addon.AddonMatchProperty;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinder;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinderConstants;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonSuggestionService;
|
||||||
|
import org.openhab.core.i18n.LocaleProvider;
|
||||||
|
import org.osgi.service.cm.Configuration;
|
||||||
|
import org.osgi.service.cm.ConfigurationAdmin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit tests for the {@link AddonSuggestionService}.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
* @author Mark Herwege - Adapted to finders in separate packages
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@TestInstance(Lifecycle.PER_CLASS)
|
||||||
|
public class AddonSuggestionServiceTests {
|
||||||
|
|
||||||
|
private @NonNullByDefault({}) ConfigurationAdmin configurationAdmin;
|
||||||
|
private @NonNullByDefault({}) LocaleProvider localeProvider;
|
||||||
|
private @NonNullByDefault({}) AddonInfoProvider addonInfoProvider;
|
||||||
|
private @NonNullByDefault({}) AddonFinder mdnsAddonFinder;
|
||||||
|
private @NonNullByDefault({}) AddonFinder upnpAddonFinder;
|
||||||
|
private @NonNullByDefault({}) AddonSuggestionService addonSuggestionService;
|
||||||
|
|
||||||
|
private final Map<String, Object> config = Map.of(AddonFinderConstants.CFG_FINDER_MDNS, true,
|
||||||
|
AddonFinderConstants.CFG_FINDER_UPNP, true);
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public void cleanUp() {
|
||||||
|
assertNotNull(addonSuggestionService);
|
||||||
|
try {
|
||||||
|
addonSuggestionService.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public void setup() {
|
||||||
|
setupMockConfigurationAdmin();
|
||||||
|
setupMockLocaleProvider();
|
||||||
|
setupMockAddonInfoProvider();
|
||||||
|
setupMockMdnsAddonFinder();
|
||||||
|
setupMockUpnpAddonFinder();
|
||||||
|
addonSuggestionService = createAddonSuggestionService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddonSuggestionService createAddonSuggestionService() {
|
||||||
|
AddonSuggestionService addonSuggestionService = new AddonSuggestionService(configurationAdmin, localeProvider,
|
||||||
|
config);
|
||||||
|
assertNotNull(addonSuggestionService);
|
||||||
|
|
||||||
|
addonSuggestionService.addAddonFinder(mdnsAddonFinder);
|
||||||
|
addonSuggestionService.addAddonFinder(upnpAddonFinder);
|
||||||
|
|
||||||
|
return addonSuggestionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockConfigurationAdmin() {
|
||||||
|
// create the mock
|
||||||
|
configurationAdmin = mock(ConfigurationAdmin.class);
|
||||||
|
Configuration configuration = mock(Configuration.class);
|
||||||
|
try {
|
||||||
|
when(configurationAdmin.getConfiguration(any())).thenReturn(configuration);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
when(configuration.getProperties()).thenReturn(null);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(configurationAdmin);
|
||||||
|
try {
|
||||||
|
assertNull(configurationAdmin.getConfiguration(AddonSuggestionService.CONFIG_PID).getProperties());
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockLocaleProvider() {
|
||||||
|
// create the mock
|
||||||
|
localeProvider = mock(LocaleProvider.class);
|
||||||
|
when(localeProvider.getLocale()).thenReturn(Locale.US);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(localeProvider);
|
||||||
|
assertEquals(Locale.US, localeProvider.getLocale());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockAddonInfoProvider() {
|
||||||
|
AddonDiscoveryMethod hp = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS)
|
||||||
|
.setMatchProperties(
|
||||||
|
List.of(new AddonMatchProperty("rp", ".*"), new AddonMatchProperty("ty", "hp (.*)")))
|
||||||
|
.setMdnsServiceType("_printer._tcp.local.");
|
||||||
|
|
||||||
|
AddonDiscoveryMethod hue1 = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_UPNP)
|
||||||
|
.setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge")));
|
||||||
|
|
||||||
|
AddonDiscoveryMethod hue2 = new AddonDiscoveryMethod().setServiceType(AddonFinderConstants.SERVICE_TYPE_MDNS)
|
||||||
|
.setMdnsServiceType("_hue._tcp.local.");
|
||||||
|
|
||||||
|
// create the mock
|
||||||
|
addonInfoProvider = mock(AddonInfoProvider.class);
|
||||||
|
Set<AddonInfo> addonInfos = new HashSet<>();
|
||||||
|
addonInfos.add(AddonInfo.builder("hue", "binding").withName("Hue").withDescription("Hue Bridge")
|
||||||
|
.withDiscoveryMethods(List.of(hue1, hue2)).build());
|
||||||
|
|
||||||
|
addonInfos.add(AddonInfo.builder("hpprinter", "binding").withName("HP").withDescription("HP Printer")
|
||||||
|
.withDiscoveryMethods(List.of(hp)).build());
|
||||||
|
when(addonInfoProvider.getAddonInfos(any(Locale.class))).thenReturn(addonInfos);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(addonInfoProvider);
|
||||||
|
Set<AddonInfo> addonInfos2 = addonInfoProvider.getAddonInfos(Locale.US);
|
||||||
|
assertEquals(2, addonInfos2.size());
|
||||||
|
assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockMdnsAddonFinder() {
|
||||||
|
// create the mock
|
||||||
|
mdnsAddonFinder = mock(AddonFinder.class);
|
||||||
|
|
||||||
|
Set<AddonInfo> addonInfos = addonInfoProvider.getAddonInfos(Locale.US).stream().filter(
|
||||||
|
c -> c.getDiscoveryMethods().stream().anyMatch(m -> SERVICE_TYPE_MDNS.equals(m.getServiceType())))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
when(mdnsAddonFinder.getSuggestedAddons()).thenReturn(addonInfos);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(mdnsAddonFinder);
|
||||||
|
Set<AddonInfo> addonInfos2 = mdnsAddonFinder.getSuggestedAddons();
|
||||||
|
assertEquals(2, addonInfos2.size());
|
||||||
|
assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMockUpnpAddonFinder() {
|
||||||
|
// create the mock
|
||||||
|
upnpAddonFinder = mock(AddonFinder.class);
|
||||||
|
|
||||||
|
Set<AddonInfo> addonInfos = addonInfoProvider.getAddonInfos(Locale.US).stream().filter(
|
||||||
|
c -> c.getDiscoveryMethods().stream().anyMatch(m -> SERVICE_TYPE_UPNP.equals(m.getServiceType())))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
when(upnpAddonFinder.getSuggestedAddons()).thenReturn(addonInfos);
|
||||||
|
|
||||||
|
// check that it works
|
||||||
|
assertNotNull(upnpAddonFinder);
|
||||||
|
Set<AddonInfo> addonInfos2 = upnpAddonFinder.getSuggestedAddons();
|
||||||
|
assertEquals(1, addonInfos2.size());
|
||||||
|
assertTrue(addonInfos2.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSuggestedAddons() {
|
||||||
|
addonSuggestionService.addAddonInfoProvider(addonInfoProvider);
|
||||||
|
Set<AddonInfo> addons = addonSuggestionService.getSuggestedAddons(localeProvider.getLocale());
|
||||||
|
assertEquals(2, addons.size());
|
||||||
|
assertFalse(addons.stream().anyMatch(a -> "aardvark".equals(a.getUID())));
|
||||||
|
assertTrue(addons.stream().anyMatch(a -> "binding-hue".equals(a.getUID())));
|
||||||
|
assertTrue(addons.stream().anyMatch(a -> "binding-hpprinter".equals(a.getUID())));
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,11 @@
|
|||||||
<artifactId>org.openhab.core.config.discovery</artifactId>
|
<artifactId>org.openhab.core.config.discovery</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.core.bundles</groupId>
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
<artifactId>org.openhab.core.io.rest</artifactId>
|
<artifactId>org.openhab.core.io.rest</artifactId>
|
||||||
|
@ -56,6 +56,7 @@ import org.openhab.core.config.core.ConfigDescription;
|
|||||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||||
import org.openhab.core.config.core.ConfigUtil;
|
import org.openhab.core.config.core.ConfigUtil;
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonSuggestionService;
|
||||||
import org.openhab.core.events.Event;
|
import org.openhab.core.events.Event;
|
||||||
import org.openhab.core.events.EventPublisher;
|
import org.openhab.core.events.EventPublisher;
|
||||||
import org.openhab.core.io.rest.JSONResponse;
|
import org.openhab.core.io.rest.JSONResponse;
|
||||||
@ -120,6 +121,7 @@ public class AddonResource implements RESTResource {
|
|||||||
private final ConfigurationService configurationService;
|
private final ConfigurationService configurationService;
|
||||||
private final AddonInfoRegistry addonInfoRegistry;
|
private final AddonInfoRegistry addonInfoRegistry;
|
||||||
private final ConfigDescriptionRegistry configDescriptionRegistry;
|
private final ConfigDescriptionRegistry configDescriptionRegistry;
|
||||||
|
private final AddonSuggestionService addonSuggestionService;
|
||||||
|
|
||||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||||
|
|
||||||
@ -127,12 +129,14 @@ public class AddonResource implements RESTResource {
|
|||||||
public AddonResource(final @Reference EventPublisher eventPublisher, final @Reference LocaleService localeService,
|
public AddonResource(final @Reference EventPublisher eventPublisher, final @Reference LocaleService localeService,
|
||||||
final @Reference ConfigurationService configurationService,
|
final @Reference ConfigurationService configurationService,
|
||||||
final @Reference AddonInfoRegistry addonInfoRegistry,
|
final @Reference AddonInfoRegistry addonInfoRegistry,
|
||||||
final @Reference ConfigDescriptionRegistry configDescriptionRegistry) {
|
final @Reference ConfigDescriptionRegistry configDescriptionRegistry,
|
||||||
|
final @Reference AddonSuggestionService addonSuggestionService) {
|
||||||
this.eventPublisher = eventPublisher;
|
this.eventPublisher = eventPublisher;
|
||||||
this.localeService = localeService;
|
this.localeService = localeService;
|
||||||
this.configurationService = configurationService;
|
this.configurationService = configurationService;
|
||||||
this.addonInfoRegistry = addonInfoRegistry;
|
this.addonInfoRegistry = addonInfoRegistry;
|
||||||
this.configDescriptionRegistry = configDescriptionRegistry;
|
this.configDescriptionRegistry = configDescriptionRegistry;
|
||||||
|
this.addonSuggestionService = addonSuggestionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||||
@ -178,6 +182,19 @@ public class AddonResource implements RESTResource {
|
|||||||
return Response.ok(new Stream2JSONInputStream(addonTypeStream)).build();
|
return Response.ok(new Stream2JSONInputStream(addonTypeStream)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/suggestions")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(operationId = "getSuggestedAddons", summary = "Get suggested add-ons to be installed.", responses = {
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Addon.class)))), })
|
||||||
|
public Response getSuggestions(
|
||||||
|
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) {
|
||||||
|
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
|
||||||
|
Locale locale = localeService.getLocale(language);
|
||||||
|
return Response.ok(new Stream2JSONInputStream(addonSuggestionService.getSuggestedAddons(locale).stream()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/types")
|
@Path("/types")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@ -30,6 +30,11 @@
|
|||||||
<artifactId>org.openhab.core.config.core</artifactId>
|
<artifactId>org.openhab.core.config.core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.core.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.karaf.features</groupId>
|
<groupId>org.apache.karaf.features</groupId>
|
||||||
<artifactId>org.apache.karaf.features.core</artifactId>
|
<artifactId>org.apache.karaf.features.core</artifactId>
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.karaf.internal;
|
||||||
|
|
||||||
|
import org.apache.karaf.features.FeaturesService;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.config.discovery.addon.AddonFinderService;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service is an implementation of an openHAB {@link AddonSuggestionFinderService} using the Karaf features
|
||||||
|
* service. This service allows dynamic installation/removal of add-on suggestion finders.
|
||||||
|
*
|
||||||
|
* @author Mark Herwege - Initial contribution
|
||||||
|
*/
|
||||||
|
@Component(name = "org.openhab.core.karafaddonfinders", immediate = true)
|
||||||
|
@NonNullByDefault
|
||||||
|
public class KarafAddonFinderService implements AddonFinderService {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(KarafAddonFinderService.class);
|
||||||
|
|
||||||
|
private final FeaturesService featuresService;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public KarafAddonFinderService(final @Reference FeaturesService featuresService) {
|
||||||
|
this.featuresService = featuresService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void install(String id) {
|
||||||
|
try {
|
||||||
|
if (!featuresService.isInstalled(featuresService.getFeature(id))) {
|
||||||
|
featuresService.installFeature(id);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to install add-on suggestion finder {}", id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uninstall(String id) {
|
||||||
|
try {
|
||||||
|
if (featuresService.isInstalled(featuresService.getFeature(id))) {
|
||||||
|
featuresService.uninstallFeature(id);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to uninstall add-on suggestion finder {}", id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -133,8 +133,7 @@ public class KarafAddonService implements AddonService {
|
|||||||
|
|
||||||
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale);
|
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale);
|
||||||
|
|
||||||
if (isInstalled && addonInfo != null) {
|
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())
|
addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription())
|
||||||
.withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries())
|
.withConnection(addonInfo.getConnection()).withCountries(addonInfo.getCountries())
|
||||||
.withLink(getDefaultDocumentationLink(type, name))
|
.withLink(getDefaultDocumentationLink(type, name))
|
||||||
|
@ -17,6 +17,18 @@
|
|||||||
expected. Enabling this option will include these entries in the list of available add-ons.</description>
|
expected. Enabling this option will include these entries in the list of available add-ons.</description>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="suggestionFinderUpnp" type="boolean">
|
||||||
|
<advanced>true</advanced>
|
||||||
|
<label>UPnP Suggestion Finder</label>
|
||||||
|
<description>Use UPnP network scan to suggest add-ons.</description>
|
||||||
|
<default>true</default>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="suggestionFinderMdns" type="boolean">
|
||||||
|
<advanced>true</advanced>
|
||||||
|
<label>mDNS Suggestion Finder</label>
|
||||||
|
<description>Use mDNS network scan to suggest add-ons.</description>
|
||||||
|
<default>true</default>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
|
||||||
</config-description:config-descriptions>
|
</config-description:config-descriptions>
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
<module>org.openhab.core.automation.rest</module>
|
<module>org.openhab.core.automation.rest</module>
|
||||||
<module>org.openhab.core.config.core</module>
|
<module>org.openhab.core.config.core</module>
|
||||||
<module>org.openhab.core.config.discovery</module>
|
<module>org.openhab.core.config.discovery</module>
|
||||||
|
<module>org.openhab.core.config.discovery.addon</module>
|
||||||
|
<module>org.openhab.core.config.discovery.addon.mdns</module>
|
||||||
|
<module>org.openhab.core.config.discovery.addon.upnp</module>
|
||||||
<module>org.openhab.core.config.discovery.mdns</module>
|
<module>org.openhab.core.config.discovery.mdns</module>
|
||||||
<module>org.openhab.core.config.discovery.usbserial</module>
|
<module>org.openhab.core.config.discovery.usbserial</module>
|
||||||
<module>org.openhab.core.config.discovery.usbserial.linuxsysfs</module>
|
<module>org.openhab.core.config.discovery.usbserial.linuxsysfs</module>
|
||||||
|
@ -66,6 +66,29 @@
|
|||||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version}</bundle>
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest/${project.version}</bundle>
|
||||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version}</bundle>
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.core/${project.version}</bundle>
|
||||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.sse/${project.version}</bundle>
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.io.rest.sse/${project.version}</bundle>
|
||||||
|
<feature>openhab-core-config-discovery-addon</feature>
|
||||||
|
</feature>
|
||||||
|
|
||||||
|
<feature name="openhab-core-config-discovery-addon" version="${project.version}">
|
||||||
|
<feature>openhab-core-base</feature>
|
||||||
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
|
||||||
|
<feature name="openhab-core-config-discovery-addon-mdns" version="${project.version}">
|
||||||
|
<feature>openhab-core-base</feature>
|
||||||
|
<feature>openhab-core-config-discovery-addon</feature>
|
||||||
|
<bundle dependency="true">mvn:org.openhab.core.bundles/org.openhab.core.io.transport.mdns/${project.version}</bundle>
|
||||||
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.mdns/${project.version}</bundle>
|
||||||
|
<requirement>openhab.tp;filter:="(feature=jmdns)"</requirement>
|
||||||
|
<feature dependency="true">openhab.tp-jmdns</feature>
|
||||||
|
</feature>
|
||||||
|
|
||||||
|
<feature name="openhab-core-config-discovery-addon-upnp" version="${project.version}">
|
||||||
|
<feature>openhab-core-base</feature>
|
||||||
|
<feature>openhab-core-config-discovery-addon</feature>
|
||||||
|
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.config.discovery.addon.upnp/${project.version}</bundle>
|
||||||
|
<requirement>openhab.tp;filter:="(feature=jupnp)"</requirement>
|
||||||
|
<feature dependency="true">openhab.tp-jupnp</feature>
|
||||||
</feature>
|
</feature>
|
||||||
|
|
||||||
<feature name="openhab-core-addon-marketplace" version="${project.version}">
|
<feature name="openhab-core-addon-marketplace" version="${project.version}">
|
||||||
|
Loading…
Reference in New Issue
Block a user