Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.addon.AddonService;
import org.openhab.core.addon.AddonType;
import org.openhab.core.common.Version;
import org.openhab.core.config.core.ConfigurableService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
Expand Down Expand Up @@ -136,7 +137,7 @@ private Addon getAddon(Bundle bundle, @Nullable Locale locale) {
String uid = type + Addon.ADDON_SEPARATOR + name;

Addon.Builder addon = Addon.create(ADDON_ID_PREFIX + uid).withType(type).withId(name)
.withContentType(ADDONS_CONTENT_TYPE).withVersion(bundle.getVersion().toString())
.withContentType(ADDONS_CONTENT_TYPE).withVersion(Version.valueOf(bundle.getVersion()))
.withAuthor(ADDONS_AUTHOR, true).withInstalled(true);

AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(uid, locale);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import org.apache.karaf.kar.KarService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.OpenHAB;
import org.openhab.core.addon.Addon;
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
Expand Down Expand Up @@ -72,7 +73,7 @@ public CommunityKarafAddonHandler(@Reference KarService karService) {
}

@Override
public boolean supports(String type, String contentType) {
public boolean supports(String type, @Nullable String contentType) {
return SUPPORTED_EXT_TYPES.contains(type) && KAR_CONTENT_TYPE.equals(contentType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.addon.AddonService;
import org.openhab.core.addon.AddonType;
import org.openhab.core.addon.dto.AddonDTO;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.common.Version;
import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventPublisher;
Expand Down Expand Up @@ -67,21 +69,12 @@ public abstract class AbstractRemoteAddonService implements AddonService {
if (compatible != 0) {
return compatible;
}
try {
// Add-on versions often contain a dash instead of a dot as separator for the qualifier (e.g. -SNAPSHOT)
// This is not a valid format and everything after the dash needs to be removed.
BundleVersion version1 = new BundleVersion(addon1.getVersion().replaceAll("-.*", ".0"));
BundleVersion version2 = new BundleVersion(addon2.getVersion().replaceAll("-.*", ".0"));

// prefer newer version over older
return version2.compareTo(version1);
} catch (IllegalArgumentException e) {
// assume they are equal (for ordering) if we can't compare the versions
return 0;
}
Version v1 = addon1.getVersion();
Version v2 = addon2.getVersion();
return v1 == null && v2 == null ? 0 : v2 == null ? 1 : v2.compareTo(v1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparator appears to violate the Comparator contract when exactly one add-on has no version.

For a versioned add-on a and an unversioned add-on b, both of these comparisons return a positive value:

compare(a, b)
compare(b, a)

The first result comes from v2 == null, while the second comes from Version.compareTo(null) returning 1. This makes the comparator non-antisymmetric and may result in incorrect ordering or an IllegalArgumentException during sorting.

Could both one-null cases be handled explicitly? It would also be useful to add tests for (null, null), (null, version), and (version, null).

};

protected final BundleVersion coreVersion;
protected final Version coreVersion;

protected final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").create();
protected final Set<MarketplaceAddonHandler> addonHandlers = new CopyOnWriteArraySet<>();
Expand All @@ -105,12 +98,12 @@ protected AbstractRemoteAddonService(EventPublisher eventPublisher, Configuratio
this.coreVersion = getCoreVersion();
}

protected BundleVersion getCoreVersion() {
return new BundleVersion(FrameworkUtil.getBundle(OpenHAB.class).getVersion().toString());
protected Version getCoreVersion() {
return Version.valueOf(FrameworkUtil.getBundle(OpenHAB.class).getVersion());
}

private Addon convertFromStorage(Map.Entry<String, @Nullable String> entry) {
Addon storedAddon = Objects.requireNonNull(gson.fromJson(entry.getValue(), Addon.class));
Addon storedAddon = Objects.requireNonNull(gson.fromJson(entry.getValue(), AddonDTO.class)).toAddon();
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(storedAddon.getType() + "-" + storedAddon.getId());
if (addonInfo != null && storedAddon.getConfigDescriptionURI().isBlank()) {
return Addon.create(storedAddon).withConfigDescriptionURI(addonInfo.getConfigDescriptionURI()).build();
Expand All @@ -125,7 +118,7 @@ public void refreshSource() {
}

private synchronized void refreshSource(boolean fetchRemoteAddons) {
if (!addonHandlers.stream().allMatch(MarketplaceAddonHandler::isReady)) {
if (addonHandlers.isEmpty() || !addonHandlers.stream().allMatch(MarketplaceAddonHandler::isReady)) {
logger.debug("Add-on service '{}' tried to refresh source before add-on handlers ready. Exiting.",
getClass());
return;
Expand Down Expand Up @@ -177,7 +170,8 @@ private synchronized void refreshSource(boolean fetchRemoteAddons) {

// check and remove duplicate uids
Map<String, List<Addon>> addonMap = new HashMap<>();
addons.forEach(a -> addonMap.computeIfAbsent(a.getUid(), k -> new ArrayList<>()).add(a));
addons.forEach(
a -> Objects.requireNonNull(addonMap.computeIfAbsent(a.getUid(), k -> new ArrayList<>())).add(a));
for (List<Addon> partialAddonList : addonMap.values()) {
if (partialAddonList.size() > 1) {
partialAddonList.stream().sorted(BY_COMPATIBLE_AND_VERSION).skip(1).forEach(addons::remove);
Expand Down Expand Up @@ -250,11 +244,13 @@ public void install(String id) {
try {
handler.install(addon);
addon.setInstalled(true);
installedAddonStorage.put(id, gson.toJson(addon));
installedAddonStorage.put(id, gson.toJson(new AddonDTO(addon)));
refreshSource(false);
postInstalledEvent(addon.getUid());
} catch (MarketplaceHandlerException e) {
postFailureEvent(addon.getUid(), e.getMessage());
logger.warn("Failed to install add-on \"{}\": {}", addon.getUid(), e.getMessage());
logger.trace("", e);
}
} else {
postFailureEvent(addon.getUid(), "Add-on is already installed.");
Expand Down Expand Up @@ -282,6 +278,8 @@ public void uninstall(String id) {
postUninstalledEvent(addon.getUid());
} catch (MarketplaceHandlerException e) {
postFailureEvent(addon.getUid(), e.getMessage());
logger.warn("Failed to uninstall add-on \"{}\": {}", addon.getUid(), e.getMessage());
logger.trace("", e);
}
} else {
installedAddonStorage.remove(id);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.core.addon.marketplace;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.Addon;

/**
Expand All @@ -38,7 +39,7 @@ public interface MarketplaceAddonHandler {
* @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);
boolean supports(String type, @Nullable String contentType);

/**
* Tells whether a given add-on is currently installed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.Addon;
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
import org.openhab.core.addon.marketplace.MarketplaceHandlerException;
Expand Down Expand Up @@ -84,7 +85,7 @@ protected void removeParser(RootUIComponentParser parser) {
}

@Override
public boolean supports(String type, String contentType) {
public boolean supports(String type, @Nullable String contentType) {
return "automation".equals(type) && BLOCKLIBRARIES_CONTENT_TYPE.equals(contentType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.concurrent.ScheduledExecutorService;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.Addon;
import org.openhab.core.addon.marketplace.MarketplaceAddonHandler;
import org.openhab.core.addon.marketplace.MarketplaceBundleInstaller;
Expand Down Expand Up @@ -65,9 +66,9 @@ public CommunityBundleAddonHandler(BundleContext bundleContext) {
}

@Override
public boolean supports(String type, String contentType) {
public boolean supports(String type, @Nullable String contentType) {
// we support only certain extension types, and only as pure OSGi bundles
return SUPPORTED_EXT_TYPES.contains(type) && contentType.equals(JAR_CONTENT_TYPE);
return SUPPORTED_EXT_TYPES.contains(type) && JAR_CONTENT_TYPE.equals(contentType);
}

@Override
Expand All @@ -89,8 +90,9 @@ public void install(Addon addon) throws MarketplaceHandlerException {
} catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
throw new MarketplaceHandlerException("Malformed source URL: " + e.getMessage(), e);
}
addBundleToCache(addon.getUid(), sourceUrl);
installFromCache(bundleContext, addon.getUid());
String addonId = addon.getUid();
addBundleToCache(addonId, sourceUrl);
installFromCache(bundleContext, addonId);
}

@Override
Expand Down
Loading