Extend marketplace to accept kar and some improvements (#2490)
Also-by: Wouter Born <github@maindrain.net> Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>pull/2493/head
parent
0d5b2d6140
commit
4569eea519
|
@ -514,6 +514,12 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.addon.marketplace.karaf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -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-11">
|
||||
<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="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</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.addon.marketplace.karaf</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>
|
|
@ -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
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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 http://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>3.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.addon.marketplace.karaf</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Community Marketplace Add-on Service :: Karaf </name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.karaf.kar</groupId>
|
||||
<artifactId>org.apache.karaf.kar.core</artifactId>
|
||||
<version>${karaf.tooling.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.addon.marketplace</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.addon.marketplace.karaf.internal.community;
|
||||
|
||||
import static org.openhab.core.addon.marketplace.internal.community.CommunityMarketplaceAddonService.KAR_CONTENT_TYPE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.karaf.kar.KarService;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.addon.Addon;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A {@link CommunityKarafAddonHandler} implementation, which handles add-ons as KAR files and installs them
|
||||
* using the {@link KarService}.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution and API
|
||||
* @author Yannick Schaus - refactoring
|
||||
* @author Jan N. Klug - refactor to support kar files
|
||||
*
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class CommunityKarafAddonHandler implements MarketplaceAddonHandler {
|
||||
private static final Path KAR_CACHE_PATH = Path.of(OpenHAB.getUserDataFolder(), "marketplace", "kar");
|
||||
private static final List<String> SUPPORTED_EXT_TYPES = List.of("automation", "binding", "io", "persistence",
|
||||
"transformation", "ui", "voice");
|
||||
private static final String KAR_DOWNLOAD_URL_PROPERTY = "kar_download_url";
|
||||
private static final String KAR_EXTENSION = ".kar";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CommunityKarafAddonHandler.class);
|
||||
|
||||
private final KarService karService;
|
||||
|
||||
@Activate
|
||||
public CommunityKarafAddonHandler(@Reference KarService karService) {
|
||||
this.karService = karService;
|
||||
ensureCachedKarsAreInstalled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type, String contentType) {
|
||||
return SUPPORTED_EXT_TYPES.contains(type) && KAR_CONTENT_TYPE.equals(contentType);
|
||||
}
|
||||
|
||||
private Stream<Path> karFilesStream(Path addonDirectory) throws IOException {
|
||||
return Files.isDirectory(addonDirectory) ? Files.list(addonDirectory).map(Path::getFileName)
|
||||
.filter(path -> path.toString().endsWith(KAR_EXTENSION)) : Stream.empty();
|
||||
}
|
||||
|
||||
private String pathToKarRepoName(Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
return fileName.substring(0, fileName.length() - KAR_EXTENSION.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public boolean isInstalled(String addonId) {
|
||||
try {
|
||||
Path addonDirectory = getAddonCacheDirectory(addonId);
|
||||
List<String> repositories = karService.list();
|
||||
return karFilesStream(addonDirectory).findFirst().map(this::pathToKarRepoName).map(repositories::contains)
|
||||
.orElse(false);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to determine installation status for {}: ", addonId, e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(Addon addon) throws MarketplaceHandlerException {
|
||||
try {
|
||||
URL sourceUrl = new URL((String) addon.getProperties().get(KAR_DOWNLOAD_URL_PROPERTY));
|
||||
addKarToCache(addon.getId(), sourceUrl);
|
||||
installFromCache(addon.getId());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new MarketplaceHandlerException("Malformed source URL: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstall(Addon addon) throws MarketplaceHandlerException {
|
||||
try {
|
||||
Path addonPath = getAddonCacheDirectory(addon.getId());
|
||||
List<String> repositories = karService.list();
|
||||
for (Path path : karFilesStream(addonPath).collect(Collectors.toList())) {
|
||||
String karRepoName = pathToKarRepoName(path);
|
||||
if (repositories.contains(karRepoName)) {
|
||||
karService.uninstall(karRepoName);
|
||||
}
|
||||
Files.delete(addonPath.resolve(path));
|
||||
}
|
||||
Files.delete(addonPath);
|
||||
} catch (Exception e) {
|
||||
throw new MarketplaceHandlerException("Failed uninstalling KAR: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a KAR file from a remote source and puts it in the local cache with the add-on ID.
|
||||
*
|
||||
* @param addonId the add-on ID
|
||||
* @param sourceUrl the (online) source where the KAR file can be found
|
||||
* @throws MarketplaceHandlerException on error
|
||||
*/
|
||||
private void addKarToCache(String addonId, URL sourceUrl) throws MarketplaceHandlerException {
|
||||
try {
|
||||
String fileName = new File(sourceUrl.toURI().getPath()).getName();
|
||||
Path addonFile = getAddonCacheDirectory(addonId).resolve(fileName);
|
||||
Files.createDirectories(addonFile.getParent());
|
||||
InputStream source = sourceUrl.openStream();
|
||||
Files.copy(source, addonFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new MarketplaceHandlerException("Cannot copy KAR to local cache: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void installFromCache(String addonId) throws MarketplaceHandlerException {
|
||||
Path addonPath = getAddonCacheDirectory(addonId);
|
||||
if (Files.isDirectory(addonPath)) {
|
||||
try {
|
||||
List<Path> karFiles = Files.list(addonPath).collect(Collectors.toList());
|
||||
if (karFiles.size() != 1) {
|
||||
throw new MarketplaceHandlerException(
|
||||
"The local cache folder doesn't contain a single file: " + addonPath, null);
|
||||
}
|
||||
try {
|
||||
karService.install(karFiles.get(0).toUri(), false);
|
||||
} catch (Exception e) {
|
||||
throw new MarketplaceHandlerException(
|
||||
"Cannot install KAR from marketplace cache: " + e.getMessage(), e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MarketplaceHandlerException("Could not list files in cache directory " + addonPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureCachedKarsAreInstalled() {
|
||||
try {
|
||||
if (Files.isDirectory(KAR_CACHE_PATH)) {
|
||||
Files.list(KAR_CACHE_PATH).filter(Files::isDirectory).map(p -> "marketplace:" + p.getFileName())
|
||||
.filter(addonId -> !isInstalled(addonId)).forEach(addonId -> {
|
||||
logger.info("Reinstalling missing marketplace KAR: {}", addonId);
|
||||
try {
|
||||
installFromCache(addonId);
|
||||
} catch (MarketplaceHandlerException e) {
|
||||
logger.warn("Failed reinstalling add-on from cache", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to re-install KARs: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Path getAddonCacheDirectory(String addonId) {
|
||||
return KAR_CACHE_PATH.resolve(addonId.replace("marketplace:", ""));
|
||||
}
|
||||
}
|
|
@ -35,8 +35,9 @@ public interface MarketplaceAddonHandler {
|
|||
/**
|
||||
* Tells whether this handler supports a given add-on.
|
||||
*
|
||||
* @param addon the add-on in question
|
||||
* @return true, if the add-on is supported, false otherwise
|
||||
* @param type the type of the add-on in question
|
||||
* @param contentType the content type of the add-on on question
|
||||
* @return true, if the addon type and contentType are supported, false otherwise
|
||||
*/
|
||||
boolean supports(String type, String contentType);
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import java.net.URL;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.OpenHAB;
|
||||
|
@ -44,8 +46,7 @@ import org.slf4j.LoggerFactory;
|
|||
public abstract class MarketplaceBundleInstaller {
|
||||
private final Logger logger = LoggerFactory.getLogger(MarketplaceBundleInstaller.class);
|
||||
|
||||
private static final String BUNDLE_CACHE_PATH = OpenHAB.getUserDataFolder() + File.separator + "marketplace"
|
||||
+ File.separator + "bundles";
|
||||
private static final Path BUNDLE_CACHE_PATH = Path.of(OpenHAB.getUserDataFolder(), "marketplace", "bundles");
|
||||
|
||||
/**
|
||||
* Downloads a bundle file from a remote source and puts it in the local cache with the add-on ID.
|
||||
|
@ -57,14 +58,13 @@ public abstract class MarketplaceBundleInstaller {
|
|||
protected void addBundleToCache(String addonId, URL sourceUrl) throws MarketplaceHandlerException {
|
||||
try {
|
||||
String fileName = new File(sourceUrl.toURI().getPath()).getName();
|
||||
File addonFile = new File(getAddonCacheDirectory(addonId), fileName);
|
||||
addonFile.getParentFile().mkdirs();
|
||||
Path addonFile = getAddonCacheDirectory(addonId).resolve(fileName);
|
||||
Files.createDirectories(addonFile.getParent());
|
||||
|
||||
InputStream source = sourceUrl.openStream();
|
||||
Path outputPath = Path.of(addonFile.toURI());
|
||||
Files.copy(source, outputPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(source, addonFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
throw new MarketplaceHandlerException("Cannot copy bundle to local cache: " + e.getMessage());
|
||||
throw new MarketplaceHandlerException("Cannot copy bundle to local cache: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,26 +76,27 @@ public abstract class MarketplaceBundleInstaller {
|
|||
* @throws MarketplaceHandlerException
|
||||
*/
|
||||
protected void installFromCache(BundleContext bundleContext, String addonId) throws MarketplaceHandlerException {
|
||||
File addonPath = getAddonCacheDirectory(addonId);
|
||||
if (addonPath.exists() && addonPath.isDirectory()) {
|
||||
File[] bundleFiles = addonPath.listFiles();
|
||||
if (bundleFiles.length != 1) {
|
||||
throw new MarketplaceHandlerException(
|
||||
"The local cache folder doesn't contain a single file: " + addonPath.toString());
|
||||
}
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(bundleFiles[0])) {
|
||||
Bundle bundle = bundleContext.installBundle(addonId, fileInputStream);
|
||||
try {
|
||||
bundle.start();
|
||||
} catch (BundleException e) {
|
||||
logger.warn("The marketplace bundle was successfully installed but doesn't start: {}",
|
||||
e.getMessage());
|
||||
Path addonPath = getAddonCacheDirectory(addonId);
|
||||
if (Files.isDirectory(addonPath)) {
|
||||
try {
|
||||
List<Path> bundleFiles = Files.list(addonPath).collect(Collectors.toList());
|
||||
if (bundleFiles.size() != 1) {
|
||||
throw new MarketplaceHandlerException(
|
||||
"The local cache folder doesn't contain a single file: " + addonPath, null);
|
||||
}
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(bundleFiles.get(0).toFile())) {
|
||||
Bundle bundle = bundleContext.installBundle(addonId, fileInputStream);
|
||||
try {
|
||||
bundle.start();
|
||||
} catch (BundleException e) {
|
||||
logger.warn("The marketplace bundle was successfully installed but doesn't start: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (IOException | BundleException e) {
|
||||
throw new MarketplaceHandlerException(
|
||||
"Cannot install bundle from marketplace cache: " + e.getMessage());
|
||||
throw new MarketplaceHandlerException("Cannot install bundle from marketplace cache: " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,21 +118,24 @@ public abstract class MarketplaceBundleInstaller {
|
|||
* @param addonId the add-on ID
|
||||
*/
|
||||
protected void uninstallBundle(BundleContext bundleContext, String addonId) throws MarketplaceHandlerException {
|
||||
File addonPath = getAddonCacheDirectory(addonId);
|
||||
if (addonPath.exists() && addonPath.isDirectory()) {
|
||||
for (File bundleFile : addonPath.listFiles()) {
|
||||
bundleFile.delete();
|
||||
try {
|
||||
Path addonPath = getAddonCacheDirectory(addonId);
|
||||
if (Files.isDirectory(addonPath)) {
|
||||
for (Path bundleFile : Files.list(addonPath).collect(Collectors.toList())) {
|
||||
Files.delete(bundleFile);
|
||||
}
|
||||
}
|
||||
Files.delete(addonPath);
|
||||
} catch (IOException e) {
|
||||
throw new MarketplaceHandlerException("Failed to delete bundle-files: " + e.getMessage(), e);
|
||||
}
|
||||
addonPath.delete();
|
||||
|
||||
if (isBundleInstalled(bundleContext, addonId)) {
|
||||
Bundle bundle = bundleContext.getBundle(addonId);
|
||||
try {
|
||||
bundle.stop();
|
||||
bundle.uninstall();
|
||||
} catch (BundleException e) {
|
||||
throw new MarketplaceHandlerException("Failed uninstalling bundle: " + e.getMessage());
|
||||
throw new MarketplaceHandlerException("Failed uninstalling bundle: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,26 +146,24 @@ public abstract class MarketplaceBundleInstaller {
|
|||
* @param bundleContext the {@link BundleContext} to use to look up the bundles
|
||||
*/
|
||||
protected void ensureCachedBundlesAreInstalled(BundleContext bundleContext) {
|
||||
File addonPath = new File(BUNDLE_CACHE_PATH);
|
||||
if (addonPath.exists() && addonPath.isDirectory()) {
|
||||
for (File bundleFile : addonPath.listFiles()) {
|
||||
if (bundleFile.isDirectory()) {
|
||||
String addonId = "marketplace:" + bundleFile.getName();
|
||||
if (!isBundleInstalled(bundleContext, addonId)) {
|
||||
logger.info("Reinstalling missing marketplace bundle: {}", addonId);
|
||||
try {
|
||||
installFromCache(bundleContext, addonId);
|
||||
} catch (MarketplaceHandlerException e) {
|
||||
logger.warn("Failed reinstalling add-on from cache", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
bundleFile.delete();
|
||||
try {
|
||||
if (Files.isDirectory(BUNDLE_CACHE_PATH)) {
|
||||
Files.list(BUNDLE_CACHE_PATH).filter(Files::isDirectory).map(p -> "marketplace:" + p.getFileName())
|
||||
.filter(addonId -> !isBundleInstalled(bundleContext, addonId)).forEach(addonId -> {
|
||||
logger.info("Reinstalling missing marketplace bundle: {}", addonId);
|
||||
try {
|
||||
installFromCache(bundleContext, addonId);
|
||||
} catch (MarketplaceHandlerException e) {
|
||||
logger.warn("Failed reinstalling add-on from cache", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to re-install bundles: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private File getAddonCacheDirectory(String addonId) {
|
||||
return new File(BUNDLE_CACHE_PATH + File.separator + addonId.replace("marketplace:", ""));
|
||||
private Path getAddonCacheDirectory(String addonId) {
|
||||
return BUNDLE_CACHE_PATH.resolve(addonId.replace("marketplace:", ""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
package org.openhab.core.addon.marketplace;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This is an exception that can be thrown by {@link MarketplaceAddonHandler}s if some operation fails.
|
||||
|
@ -30,7 +31,7 @@ public class MarketplaceHandlerException extends Exception {
|
|||
*
|
||||
* @param message A message describing the issue
|
||||
*/
|
||||
public MarketplaceHandlerException(String message) {
|
||||
super(message);
|
||||
public MarketplaceHandlerException(String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,13 @@
|
|||
*/
|
||||
package org.openhab.core.addon.marketplace.internal.community;
|
||||
|
||||
import static org.openhab.core.addon.marketplace.internal.community.CommunityMarketplaceAddonService.JAR_CONTENT_TYPE;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.addon.Addon;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceBundleInstaller;
|
||||
|
@ -25,9 +26,6 @@ import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
|
|||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link MarketplaceAddonHandler} implementation, which handles add-ons as jar files (specifically, OSGi
|
||||
|
@ -40,34 +38,24 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class CommunityBundleAddonHandler extends MarketplaceBundleInstaller implements MarketplaceAddonHandler {
|
||||
|
||||
// add-on types supported by this handler
|
||||
private static final List<String> SUPPORTED_EXT_TYPES = Arrays.asList("binding");
|
||||
|
||||
private static final String BUNDLE_CONTENTTYPE = "application/vnd.openhab.bundle";
|
||||
|
||||
private static final List<String> SUPPORTED_EXT_TYPES = List.of("automation", "binding", "io", "persistence",
|
||||
"transformation", "ui", "voice");
|
||||
private static final String JAR_DOWNLOAD_URL_PROPERTY = "jar_download_url";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CommunityBundleAddonHandler.class);
|
||||
|
||||
private BundleContext bundleContext;
|
||||
private final BundleContext bundleContext;
|
||||
|
||||
@Activate
|
||||
protected void activate(BundleContext bundleContext, Map<String, Object> config) {
|
||||
public CommunityBundleAddonHandler(BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
ensureCachedBundlesAreInstalled(bundleContext);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
this.bundleContext = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type, String contentType) {
|
||||
// we support only certain extension types, and only as pure OSGi bundles
|
||||
return SUPPORTED_EXT_TYPES.contains(type) && contentType.equals(BUNDLE_CONTENTTYPE);
|
||||
return SUPPORTED_EXT_TYPES.contains(type) && contentType.equals(JAR_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -77,13 +65,12 @@ public class CommunityBundleAddonHandler extends MarketplaceBundleInstaller impl
|
|||
|
||||
@Override
|
||||
public void install(Addon addon) throws MarketplaceHandlerException {
|
||||
URL sourceUrl;
|
||||
try {
|
||||
sourceUrl = new URL((String) addon.getProperties().get(JAR_DOWNLOAD_URL_PROPERTY));
|
||||
URL sourceUrl = new URL((String) addon.getProperties().get(JAR_DOWNLOAD_URL_PROPERTY));
|
||||
addBundleToCache(addon.getId(), sourceUrl);
|
||||
installFromCache(bundleContext, addon.getId());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new MarketplaceHandlerException("Malformed source URL: " + e.getMessage());
|
||||
throw new MarketplaceHandlerException("Malformed source URL: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
*/
|
||||
package org.openhab.core.addon.marketplace.internal.community;
|
||||
|
||||
import static org.openhab.core.addon.Addon.CODE_MATURITY_LEVELS;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
|
@ -25,6 +27,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -36,12 +39,12 @@ import org.openhab.core.addon.AddonService;
|
|||
import org.openhab.core.addon.AddonType;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponse;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponse.DiscoursePosterInfo;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponse.DiscourseTopicItem;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponse.DiscourseUser;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseTopicResponse;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseTopicResponse.DiscoursePostLink;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponseDTO;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponseDTO.DiscoursePosterInfo;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponseDTO.DiscourseTopicItem;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseCategoryResponseDTO.DiscourseUser;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseTopicResponseDTO;
|
||||
import org.openhab.core.addon.marketplace.internal.community.model.DiscourseTopicResponseDTO.DiscoursePostLink;
|
||||
import org.openhab.core.config.core.ConfigurableService;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
|
@ -68,14 +71,16 @@ import com.google.gson.GsonBuilder;
|
|||
property = Constants.SERVICE_PID + "=org.openhab.marketplace")
|
||||
@ConfigurableService(category = "system", label = "Community Marketplace", description_uri = CommunityMarketplaceAddonService.CONFIG_URI)
|
||||
public class CommunityMarketplaceAddonService implements AddonService {
|
||||
public static final String JAR_CONTENT_TYPE = "application/vnd.openhab.bundle";
|
||||
public static final String KAR_CONTENT_TYPE = "application/vnd.openhab.feature;type=karfile";
|
||||
public static final String RULETEMPLATES_CONTENT_TYPE = "application/vnd.openhab.ruletemplate";
|
||||
public static final String UIWIDGETS_CONTENT_TYPE = "application/vnd.openhab.uicomponent;type=widget";
|
||||
|
||||
// constants for the configuration properties
|
||||
static final String CONFIG_URI = "system:marketplace";
|
||||
static final String CONFIG_API_KEY = "apiKey";
|
||||
static final String CONFIG_SHOW_UNPUBLISHED_ENTRIES_KEY = "showUnpublished";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CommunityMarketplaceAddonService.class);
|
||||
|
||||
private static final String COMMUNITY_BASE_URL = "https://community.openhab.org";
|
||||
private static final String COMMUNITY_MARKETPLACE_URL = COMMUNITY_BASE_URL + "/c/marketplace/69/l/latest";
|
||||
private static final String COMMUNITY_TOPIC_URL = COMMUNITY_BASE_URL + "/t/";
|
||||
|
@ -86,33 +91,31 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
private static final String YAML_CODE_MARKUP_START = "<pre><code class=\"lang-yaml\">";
|
||||
private static final String CODE_MARKUP_END = "</code></pre>";
|
||||
|
||||
private HashMap<Integer, AddonType> types = new HashMap<Integer, AddonType>(3);
|
||||
private static final Integer BINDINGS_CATEGORY = 73;
|
||||
private static final Integer BUNDLES_CATEGORY = 73;
|
||||
private static final Integer RULETEMPLATES_CATEGORY = 74;
|
||||
private static final Integer UIWIDGETS_CATEGORY = 75;
|
||||
|
||||
private static final String PUBLISHED_TAG = "published";
|
||||
|
||||
private HashMap<String, String> contentTypes = new HashMap<String, String>(3);
|
||||
private static final String BINDINGS_CONTENT_TYPE = "application/vnd.openhab.bundle";
|
||||
private static final String RULETEMPLATES_CONTENT_TYPE = "application/vnd.openhab.ruletemplate";
|
||||
private static final String UIWIDGETS_CONTENT_TYPE = "application/vnd.openhab.uicomponent;type=widget";
|
||||
private static final Map<String, AddonType> TAG_ADDON_TYPE_MAP = Map.of( //
|
||||
"automation", new AddonType("automation", "Automation"), //
|
||||
"binding", new AddonType("binding", "Bindings"), //
|
||||
"io", new AddonType("io", "I/O Services"), //
|
||||
"persistence", new AddonType("persistence", "Persistence Services"), //
|
||||
"transformation", new AddonType("transformation", "Transformations"), //
|
||||
"ui", new AddonType("ui", "User Interfaces"), //
|
||||
"voice", new AddonType("voice", "Voices"));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CommunityMarketplaceAddonService.class);
|
||||
private final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").create();
|
||||
|
||||
private final Set<MarketplaceAddonHandler> addonHandlers = new HashSet<>();
|
||||
|
||||
private EventPublisher eventPublisher;
|
||||
private String apiKey = null;
|
||||
private boolean showUnpublished = false;
|
||||
|
||||
@Activate
|
||||
protected void activate(Map<String, Object> config) {
|
||||
types.put(BINDINGS_CATEGORY, new AddonType("binding", "Bindings"));
|
||||
types.put(RULETEMPLATES_CATEGORY, new AddonType("automation", "Automation"));
|
||||
types.put(UIWIDGETS_CATEGORY, new AddonType("ui", "User Interfaces"));
|
||||
contentTypes.put("binding", BINDINGS_CONTENT_TYPE);
|
||||
contentTypes.put("automation", RULETEMPLATES_CONTENT_TYPE);
|
||||
contentTypes.put("ui", UIWIDGETS_CONTENT_TYPE);
|
||||
modified(config);
|
||||
}
|
||||
|
||||
|
@ -161,7 +164,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
@Override
|
||||
public List<Addon> getAddons(Locale locale) {
|
||||
try {
|
||||
List<DiscourseCategoryResponse> pages = new ArrayList<DiscourseCategoryResponse>();
|
||||
List<DiscourseCategoryResponseDTO> pages = new ArrayList<>();
|
||||
|
||||
URL url = new URL(COMMUNITY_MARKETPLACE_URL);
|
||||
int pageNb = 1;
|
||||
|
@ -173,7 +176,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(connection.getInputStream())) {
|
||||
DiscourseCategoryResponse parsed = gson.fromJson(reader, DiscourseCategoryResponse.class);
|
||||
DiscourseCategoryResponseDTO parsed = gson.fromJson(reader, DiscourseCategoryResponseDTO.class);
|
||||
pages.add(parsed);
|
||||
|
||||
if (parsed.topic_list.more_topics_url != null) {
|
||||
|
@ -191,7 +194,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
.map(t -> convertTopicItemToAddon(t, users)).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to retrieve marketplace add-ons", e);
|
||||
return new ArrayList<Addon>();
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,7 +210,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
}
|
||||
|
||||
try (Reader reader = new InputStreamReader(connection.getInputStream())) {
|
||||
DiscourseTopicResponse parsed = gson.fromJson(reader, DiscourseTopicResponse.class);
|
||||
DiscourseTopicResponseDTO parsed = gson.fromJson(reader, DiscourseTopicResponseDTO.class);
|
||||
return convertTopicToAddon(parsed);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -217,7 +220,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
|
||||
@Override
|
||||
public List<AddonType> getTypes(Locale locale) {
|
||||
return new ArrayList<AddonType>(types.values());
|
||||
return new ArrayList<>(TAG_ADDON_TYPE_MAP.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -270,6 +273,45 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
return "";
|
||||
}
|
||||
|
||||
private @Nullable AddonType getAddonType(@Nullable Integer category, String[] tags) {
|
||||
// check if we can determine the addon type from the category
|
||||
if (RULETEMPLATES_CATEGORY.equals(category)) {
|
||||
return TAG_ADDON_TYPE_MAP.get("automation");
|
||||
} else if (UIWIDGETS_CATEGORY.equals(category)) {
|
||||
return TAG_ADDON_TYPE_MAP.get("ui");
|
||||
} else if (BUNDLES_CATEGORY.equals(category)) {
|
||||
// try to get it from tags if we have tags
|
||||
for (String tag : tags) {
|
||||
AddonType addonType = TAG_ADDON_TYPE_MAP.get(tag);
|
||||
if (addonType != null) {
|
||||
return addonType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// or return null
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getContentType(@Nullable Integer category, String[] tags) {
|
||||
// check if we can determine the addon type from the category
|
||||
if (RULETEMPLATES_CATEGORY.equals(category)) {
|
||||
return RULETEMPLATES_CONTENT_TYPE;
|
||||
} else if (UIWIDGETS_CATEGORY.equals(category)) {
|
||||
return UIWIDGETS_CONTENT_TYPE;
|
||||
} else if (BUNDLES_CATEGORY.equals(category)) {
|
||||
if (Arrays.asList(tags).contains("kar")) {
|
||||
return KAR_CONTENT_TYPE;
|
||||
} else {
|
||||
// default to plain jar bundle for addons
|
||||
return JAR_CONTENT_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
// empty string if content type could not be defined
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a {@link DiscourseTopicItem} to a {@link Addon}
|
||||
*
|
||||
|
@ -278,16 +320,18 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
*/
|
||||
private Addon convertTopicItemToAddon(DiscourseTopicItem topic, List<DiscourseUser> users) {
|
||||
String id = ADDON_ID_PREFIX + topic.id.toString();
|
||||
AddonType addonType = types.get(topic.category_id);
|
||||
String[] tags = Objects.requireNonNullElse(topic.tags, new String[0]);
|
||||
|
||||
AddonType addonType = getAddonType(topic.category_id, tags);
|
||||
String type = (addonType != null) ? addonType.getId() : "";
|
||||
String contentType = (contentTypes.get(type) != null) ? contentTypes.get(type) : "";
|
||||
String contentType = getContentType(topic.category_id, tags);
|
||||
|
||||
String version = "";
|
||||
String title = topic.title;
|
||||
String link = COMMUNITY_TOPIC_URL + topic.id.toString();
|
||||
int likeCount = topic.like_count;
|
||||
int views = topic.views;
|
||||
int postsCount = topic.posts_count;
|
||||
String[] tags = topic.tags;
|
||||
Date createdDate = topic.created_at;
|
||||
String author = "";
|
||||
boolean verifiedAuthor = false;
|
||||
|
@ -297,6 +341,14 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
}
|
||||
}
|
||||
|
||||
String maturity = null;
|
||||
for (String tag : tags) {
|
||||
if (CODE_MATURITY_LEVELS.contains(tag)) {
|
||||
maturity = tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, Object> properties = new HashMap<>(10);
|
||||
properties.put("created_at", createdDate);
|
||||
properties.put("like_count", likeCount);
|
||||
|
@ -323,9 +375,9 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
String connection = "";
|
||||
String backgroundColor = "";
|
||||
String imageLink = topic.image_url;
|
||||
Addon addon = new Addon(id, type, title, version, contentType, link, author, verifiedAuthor, installed,
|
||||
description, detailedDescription, configDescriptionURI, keywords, countries, connection,
|
||||
backgroundColor, imageLink, properties);
|
||||
Addon addon = new Addon(id, type, title, version, maturity, contentType, link, author, verifiedAuthor,
|
||||
installed, description, detailedDescription, configDescriptionURI, keywords, countries, null,
|
||||
connection, backgroundColor, imageLink, properties);
|
||||
return addon;
|
||||
}
|
||||
|
||||
|
@ -341,29 +393,39 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Transforms a {@link DiscourseTopicResponse} to a {@link Addon}
|
||||
* Transforms a {@link DiscourseTopicResponseDTO} to a {@link Addon}
|
||||
*
|
||||
* @param topic the topic
|
||||
* @return the list item
|
||||
*/
|
||||
private Addon convertTopicToAddon(DiscourseTopicResponse topic) {
|
||||
private Addon convertTopicToAddon(DiscourseTopicResponseDTO topic) {
|
||||
String id = ADDON_ID_PREFIX + topic.id.toString();
|
||||
AddonType addonType = types.get(topic.category_id);
|
||||
String[] tags = Objects.requireNonNullElse(topic.tags, new String[0]);
|
||||
|
||||
AddonType addonType = getAddonType(topic.category_id, tags);
|
||||
String type = (addonType != null) ? addonType.getId() : "";
|
||||
String contentType = contentTypes.get(type);
|
||||
String contentType = getContentType(topic.category_id, tags);
|
||||
|
||||
String version = "";
|
||||
String title = topic.title;
|
||||
String link = COMMUNITY_TOPIC_URL + topic.id.toString();
|
||||
int likeCount = topic.like_count;
|
||||
int views = topic.views;
|
||||
int postsCount = topic.posts_count;
|
||||
String[] tags = topic.tags;
|
||||
Date createdDate = topic.post_stream.posts[0].created_at;
|
||||
Date updatedDate = topic.post_stream.posts[0].updated_at;
|
||||
Date lastPostedDate = topic.last_posted;
|
||||
String author = topic.post_stream.posts[0].display_username;
|
||||
boolean verifiedAuthor = false;
|
||||
|
||||
String maturity = null;
|
||||
for (String tag : tags) {
|
||||
if (CODE_MATURITY_LEVELS.contains(tag)) {
|
||||
maturity = tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, Object> properties = new HashMap<>(10);
|
||||
properties.put("created_at", createdDate);
|
||||
properties.put("updated_at", updatedDate);
|
||||
|
@ -382,6 +444,9 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
if (postLink.url.endsWith(".jar")) {
|
||||
properties.put("jar_download_url", postLink.url);
|
||||
}
|
||||
if (postLink.url.endsWith(".kar")) {
|
||||
properties.put("kar_download_url", postLink.url);
|
||||
}
|
||||
if (postLink.url.endsWith(".json")) {
|
||||
properties.put("json_download_url", postLink.url);
|
||||
}
|
||||
|
@ -406,7 +471,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
// try to use an handler to determine if the add-on is installed
|
||||
boolean installed = false;
|
||||
for (MarketplaceAddonHandler handler : addonHandlers) {
|
||||
if (handler.supports(type, (contentType != null) ? contentType : "")) {
|
||||
if (handler.supports(type, contentType)) {
|
||||
if (handler.isInstalled(id)) {
|
||||
installed = true;
|
||||
}
|
||||
|
@ -418,9 +483,9 @@ public class CommunityMarketplaceAddonService implements AddonService {
|
|||
String countries = "";
|
||||
String connection = "";
|
||||
String backgroundColor = "";
|
||||
Addon addon = new Addon(id, type, title, version, contentType, link, author, verifiedAuthor, installed,
|
||||
description, detailedDescription, configDescriptionURI, keywords, countries, connection,
|
||||
backgroundColor, null, properties);
|
||||
Addon addon = new Addon(id, type, title, version, maturity, contentType, link, author, verifiedAuthor,
|
||||
installed, description, detailedDescription, configDescriptionURI, keywords, countries, null,
|
||||
connection, backgroundColor, null, properties);
|
||||
return addon;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,14 @@ import java.io.InputStream;
|
|||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.addon.Addon;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
|
||||
import org.openhab.core.addon.marketplace.internal.automation.MarketplaceRuleTemplateProvider;
|
||||
import org.openhab.core.automation.template.RuleTemplateProvider;
|
||||
import org.openhab.core.storage.Storage;
|
||||
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;
|
||||
|
@ -38,7 +39,8 @@ import org.slf4j.LoggerFactory;
|
|||
* @author Yannick Schaus - refactoring
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class CommunityRuleTemplateAddonHandler implements MarketplaceAddonHandler {
|
||||
private static final String JSON_DOWNLOAD_URL_PROPERTY = "json_download_url";
|
||||
private static final String YAML_DOWNLOAD_URL_PROPERTY = "yaml_download_url";
|
||||
|
@ -48,18 +50,14 @@ public class CommunityRuleTemplateAddonHandler implements MarketplaceAddonHandle
|
|||
|
||||
private final Logger logger = LoggerFactory.getLogger(CommunityRuleTemplateAddonHandler.class);
|
||||
|
||||
private MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider;
|
||||
private final MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider;
|
||||
|
||||
@Reference
|
||||
protected void setMarketplaceRuleTemplateProvider(MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider) {
|
||||
@Activate
|
||||
public CommunityRuleTemplateAddonHandler(
|
||||
@Reference MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider) {
|
||||
this.marketplaceRuleTemplateProvider = marketplaceRuleTemplateProvider;
|
||||
}
|
||||
|
||||
protected void unsetMarketplaceRuleTemplateProvider(
|
||||
MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider) {
|
||||
this.marketplaceRuleTemplateProvider = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type, String contentType) {
|
||||
return "automation".equals(type) && RULETEMPLATES_CONTENT_TYPE.equals(contentType);
|
||||
|
@ -81,18 +79,18 @@ public class CommunityRuleTemplateAddonHandler implements MarketplaceAddonHandle
|
|||
template = getTemplateFromURL((String) addon.getProperties().get(YAML_DOWNLOAD_URL_PROPERTY));
|
||||
marketplaceRuleTemplateProvider.addTemplateAsYAML(addon.getId(), template);
|
||||
} else if (addon.getProperties().containsKey(JSON_CONTENT_PROPERTY)) {
|
||||
template = (@NonNull String) addon.getProperties().get(JSON_CONTENT_PROPERTY);
|
||||
template = (String) addon.getProperties().get(JSON_CONTENT_PROPERTY);
|
||||
marketplaceRuleTemplateProvider.addTemplateAsJSON(addon.getId(), template);
|
||||
} else if (addon.getProperties().containsKey(YAML_CONTENT_PROPERTY)) {
|
||||
template = (@NonNull String) addon.getProperties().get(YAML_CONTENT_PROPERTY);
|
||||
template = (String) addon.getProperties().get(YAML_CONTENT_PROPERTY);
|
||||
marketplaceRuleTemplateProvider.addTemplateAsYAML(addon.getId(), template);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Rule template from marketplace cannot be downloaded: {}", e.getMessage());
|
||||
throw new MarketplaceHandlerException("Template cannot be downloaded.");
|
||||
throw new MarketplaceHandlerException("Template cannot be downloaded.", e);
|
||||
} catch (Exception e) {
|
||||
logger.error("Rule template from marketplace is invalid: {}", e.getMessage());
|
||||
throw new MarketplaceHandlerException("Template is not valid.");
|
||||
throw new MarketplaceHandlerException("Template is not valid.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import java.net.URL;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.addon.Addon;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
|
||||
import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
|
||||
|
@ -41,7 +41,8 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
|||
* @author Yannick Schaus - Initial contribution and API
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@Component(immediate = true)
|
||||
@NonNullByDefault
|
||||
public class CommunityUIWidgetAddonHandler implements MarketplaceAddonHandler {
|
||||
private static final String YAML_DOWNLOAD_URL_PROPERTY = "yaml_download_url";
|
||||
private static final String YAML_CONTENT_PROPERTY = "yaml_content";
|
||||
|
@ -77,17 +78,17 @@ public class CommunityUIWidgetAddonHandler implements MarketplaceAddonHandler {
|
|||
if (addon.getProperties().containsKey(YAML_DOWNLOAD_URL_PROPERTY)) {
|
||||
widget = getWidgetFromURL((String) addon.getProperties().get(YAML_DOWNLOAD_URL_PROPERTY));
|
||||
} else if (addon.getProperties().containsKey(YAML_CONTENT_PROPERTY)) {
|
||||
widget = (@NonNull String) addon.getProperties().get(YAML_CONTENT_PROPERTY);
|
||||
widget = (String) addon.getProperties().get(YAML_CONTENT_PROPERTY);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Couldn't find the widget in the add-on entry");
|
||||
}
|
||||
addWidgetAsYAML(addon.getId(), widget);
|
||||
} catch (IOException e) {
|
||||
logger.error("Widget from marketplace cannot be downloaded: {}", e.getMessage());
|
||||
throw new MarketplaceHandlerException("Widget cannot be downloaded.");
|
||||
throw new MarketplaceHandlerException("Widget cannot be downloaded.", e);
|
||||
} catch (Exception e) {
|
||||
logger.error("Widget from marketplace is invalid: {}", e.getMessage());
|
||||
throw new MarketplaceHandlerException("Widget is not valid.");
|
||||
throw new MarketplaceHandlerException("Widget is not valid.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.Date;
|
|||
* @author Yannick Schaus - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class DiscourseCategoryResponse {
|
||||
public class DiscourseCategoryResponseDTO {
|
||||
public DiscourseUser[] users;
|
||||
public DiscourseTopicList topic_list;
|
||||
|
|
@ -20,7 +20,7 @@ import java.util.Date;
|
|||
* @author Yannick Schaus - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class DiscourseTopicResponse {
|
||||
public class DiscourseTopicResponseDTO {
|
||||
public Integer id;
|
||||
|
||||
public DiscoursePostStream post_stream;
|
|
@ -80,8 +80,9 @@ public class SampleAddonService implements AddonService {
|
|||
String description = createDescription();
|
||||
String imageLink = null;
|
||||
String backgroundColor = createRandomColor();
|
||||
Addon extension = new Addon(id, typeId, label, version, "example/vnd.openhab.addon", link, "John Doe",
|
||||
false, installed, description, backgroundColor, imageLink, null, null, null, null, null, null);
|
||||
Addon extension = new Addon(id, typeId, label, version, "stable", "example/vnd.openhab.addon", link,
|
||||
"John Doe", false, installed, description, backgroundColor, imageLink, null, null, null, null,
|
||||
null, null, null);
|
||||
extensions.put(extension.getId(), extension);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
package org.openhab.core.addon;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class defines an add-on.
|
||||
|
@ -21,10 +22,12 @@ import java.util.Map;
|
|||
* @author Yannick Schaus - Add fields
|
||||
*/
|
||||
public class Addon {
|
||||
public static final Set<String> CODE_MATURITY_LEVELS = Set.of("alpha", "beta", "mature", "stable");
|
||||
|
||||
private final String id;
|
||||
private final String label;
|
||||
private final String version;
|
||||
private final String maturity;
|
||||
private final String contentType;
|
||||
private final String link;
|
||||
private final String author;
|
||||
|
@ -36,6 +39,7 @@ public class Addon {
|
|||
private final String configDescriptionURI;
|
||||
private final String keywords;
|
||||
private final String countries;
|
||||
private final String license;
|
||||
private final String connection;
|
||||
private final String backgroundColor;
|
||||
private final String imageLink;
|
||||
|
@ -56,8 +60,8 @@ public class Addon {
|
|||
*/
|
||||
public Addon(String id, String type, String label, String version, String contentType, String link, String author,
|
||||
boolean verifiedAuthor, boolean installed) {
|
||||
this(id, type, label, version, contentType, link, author, verifiedAuthor, installed, null, null, null, null,
|
||||
null, null, null, null, null);
|
||||
this(id, type, label, version, null, contentType, link, author, verifiedAuthor, installed, null, null, null,
|
||||
null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,28 +71,39 @@ public class Addon {
|
|||
* @param type the type id of the add-on
|
||||
* @param label the label of the add-on
|
||||
* @param version the version of the add-on
|
||||
* @param maturity the maturity level of this version
|
||||
* @param contentType the content type of the add-on
|
||||
* @param description the detailed description of the add-on (may be null)
|
||||
* @param backgroundColor for displaying the add-on (may be null)
|
||||
* @param link the link to find more information about the add-on (may be null)
|
||||
* @param link the link to find more information about the add-on (may be null)*
|
||||
* @param author the author of the add-on
|
||||
* @param verifiedAuthor true, if the author is verified
|
||||
* @param imageLink the link to an image (png/svg) (may be null)
|
||||
* @param installed true, if the add-on is installed, false otherwise
|
||||
* @param description the description of the add-on (may be null)
|
||||
* @param detailedDescription the detailed description of the add-on (may be null)
|
||||
* @param configDescriptionURI the URI to the configuration description for this add-on
|
||||
* @param keywords the keywords for this add-on
|
||||
* @param countries a comma-separated list of ISO 3166 codes relevant to this add-on
|
||||
* @param license the SPDX license identifier
|
||||
* @param connection a string describing the type of connection (local or cloud, push or pull...) this add-on uses,
|
||||
* if applicable.
|
||||
* @param backgroundColor for displaying the add-on (may be null)
|
||||
* @param imageLink the link to an image (png/svg) (may be null)
|
||||
* @param properties a {@link Map} containing addition information
|
||||
*/
|
||||
public Addon(String id, String type, String label, String version, String contentType, String link, String author,
|
||||
boolean verifiedAuthor, boolean installed, String description, String detailedDescription,
|
||||
String configDescriptionURI, String keywords, String countries, String connection, String backgroundColor,
|
||||
String imageLink, Map<String, Object> properties) {
|
||||
public Addon(String id, String type, String label, String version, String maturity, String contentType, String link,
|
||||
String author, boolean verifiedAuthor, boolean installed, String description, String detailedDescription,
|
||||
String configDescriptionURI, String keywords, String countries, String license, String connection,
|
||||
String backgroundColor, String imageLink, Map<String, Object> properties) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.version = version;
|
||||
this.maturity = maturity;
|
||||
this.contentType = contentType;
|
||||
this.description = description;
|
||||
this.detailedDescription = detailedDescription;
|
||||
this.configDescriptionURI = configDescriptionURI;
|
||||
this.keywords = keywords;
|
||||
this.countries = countries;
|
||||
this.license = license;
|
||||
this.connection = connection;
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.link = link;
|
||||
|
@ -149,6 +164,13 @@ public class Addon {
|
|||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maturity level of this version
|
||||
*/
|
||||
public String getMaturity() {
|
||||
return maturity;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content type of the add-on
|
||||
*/
|
||||
|
@ -191,6 +213,13 @@ public class Addon {
|
|||
return countries;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SPDX License identifier for this addon
|
||||
*/
|
||||
public String getLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string describing the type of connection (local or cloud, push or pull...) this add-on uses, if applicable.
|
||||
*/
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<modules>
|
||||
<module>org.openhab.core.addon.marketplace</module>
|
||||
<module>org.openhab.core.addon.marketplace.karaf</module>
|
||||
<module>org.openhab.core.auth.jaas</module>
|
||||
<module>org.openhab.core.auth.oauth2client</module>
|
||||
<module>org.openhab.core.automation</module>
|
||||
|
|
|
@ -65,8 +65,10 @@
|
|||
</feature>
|
||||
|
||||
<feature name="openhab-core-addon-marketplace" version="${project.version}">
|
||||
<feature>kar</feature>
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.addon.marketplace/${project.version}</bundle>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.addon.marketplace.karaf/${project.version}</bundle>
|
||||
<requirement>openhab.tp;filter:="(feature=jackson)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jackson</feature>
|
||||
<feature dependency="true">openhab-core-ui</feature>
|
||||
|
|
Loading…
Reference in New Issue