Skip to content

Commit

Permalink
[marketplace] Add add-on handler for block libraries (openhab#2598)
Browse files Browse the repository at this point in the history
Related to openhab/openhab-webui#1225.

This add-on handler supports installing block libraries in the
UIComponentRegistry, `ui:blocks` namespace.

It is very similar to the CommunityUIWidgetAddonHandler, as the format
is the same, so are the features and implementation, the only
differences being:

- Supported content type: `application/vnd.openhab.uicomponent;type=blocks`
  (vs. `application/vnd.openhab.uicomponent;type=widget`)
- Discourse category nb.: 76 (vs. 75)
- Inferred Add-on type: "automation" (vs. "ui")

Signed-off-by: Yannick Schaus <github@schaus.net>
  • Loading branch information
ghys committed Dec 10, 2021
1 parent 0028a3c commit f641d1d
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* 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.internal.community;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;

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.ui.components.RootUIComponent;
import org.openhab.core.ui.components.UIComponentRegistry;
import org.openhab.core.ui.components.UIComponentRegistryFactory;
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;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

/**
* A {@link MarketplaceAddonHandler} implementation, which handles block libraries as YAML files and installs
* them by adding them to the {@link UIComponentRegistry} for the ui:blocks namespace.
*
* @author Yannick Schaus - Initial contribution and API
*
*/
@Component(immediate = true)
@NonNullByDefault
public class CommunityBlockLibaryAddonHandler implements MarketplaceAddonHandler {
private static final String YAML_DOWNLOAD_URL_PROPERTY = "yaml_download_url";
private static final String YAML_CONTENT_PROPERTY = "yaml_content";
private static final String BLOCKLIBRARIES_CONTENT_TYPE = "application/vnd.openhab.uicomponent;type=blocks";

private final Logger logger = LoggerFactory.getLogger(CommunityBlockLibaryAddonHandler.class);
ObjectMapper yamlMapper;

private UIComponentRegistry blocksRegistry;

@Activate
public CommunityBlockLibaryAddonHandler(final @Reference UIComponentRegistryFactory uiComponentRegistryFactory) {
this.blocksRegistry = uiComponentRegistryFactory.getRegistry("ui:blocks");
this.yamlMapper = new ObjectMapper(new YAMLFactory());
yamlMapper.findAndRegisterModules();
this.yamlMapper.setDateFormat(new SimpleDateFormat("MMM d, yyyy, hh:mm:ss aa"));
}

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

@Override
public boolean isInstalled(String id) {
return blocksRegistry.getAll().stream().anyMatch(w -> w.hasTag(id));
}

@Override
public void install(Addon addon) throws MarketplaceHandlerException {
try {
String yamlDownloadUrl = (String) addon.getProperties().get(YAML_DOWNLOAD_URL_PROPERTY);
String yamlContent = (String) addon.getProperties().get(YAML_CONTENT_PROPERTY);

if (yamlDownloadUrl != null) {
addWidgetAsYAML(addon.getId(), getWidgetFromURL(yamlDownloadUrl));
} else if (yamlContent != null) {
addWidgetAsYAML(addon.getId(), yamlContent);
} else {
throw new IllegalArgumentException("Couldn't find the block library in the add-on entry");
}
} catch (IOException e) {
logger.error("Block library from marketplace cannot be downloaded: {}", e.getMessage());
throw new MarketplaceHandlerException("Widget cannot be downloaded.", e);
} catch (Exception e) {
logger.error("Block library from marketplace is invalid: {}", e.getMessage());
throw new MarketplaceHandlerException("Widget is not valid.", e);
}
}

@Override
public void uninstall(Addon addon) throws MarketplaceHandlerException {
blocksRegistry.getAll().stream().filter(w -> w.hasTag(addon.getId())).forEach(w -> {
blocksRegistry.remove(w.getUID());
});
}

private String getWidgetFromURL(String urlString) throws IOException {
URL u = new URL(urlString);
try (InputStream in = u.openStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}

private void addWidgetAsYAML(String id, String yaml) {
try {
RootUIComponent widget = yamlMapper.readValue(yaml, RootUIComponent.class);
// add a tag with the add-on ID to be able to identify the block library in the registry
widget.addTag(id);
blocksRegistry.add(widget);
} catch (IOException e) {
logger.error("Unable to parse YAML: {}", e.getMessage());
throw new IllegalArgumentException("Unable to parse YAML");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
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";
public static final String BLOCKLIBRARIES_CONTENT_TYPE = "application/vnd.openhab.uicomponent;type=blocks";

// constants for the configuration properties
static final String CONFIG_URI = "system:marketplace";
Expand All @@ -94,6 +95,7 @@ public class CommunityMarketplaceAddonService implements AddonService {
private static final Integer BUNDLES_CATEGORY = 73;
private static final Integer RULETEMPLATES_CATEGORY = 74;
private static final Integer UIWIDGETS_CATEGORY = 75;
private static final Integer BLOCKLIBRARIES_CATEGORY = 76;

private static final String PUBLISHED_TAG = "published";

Expand Down Expand Up @@ -281,6 +283,8 @@ public String getAddonId(URI addonURI) {
return TAG_ADDON_TYPE_MAP.get("automation");
} else if (UIWIDGETS_CATEGORY.equals(category)) {
return TAG_ADDON_TYPE_MAP.get("ui");
} else if (BLOCKLIBRARIES_CATEGORY.equals(category)) {
return TAG_ADDON_TYPE_MAP.get("automation");
} else if (BUNDLES_CATEGORY.equals(category)) {
// try to get it from tags if we have tags
return tags.stream().map(TAG_ADDON_TYPE_MAP::get).filter(Objects::nonNull).findFirst().orElse(null);
Expand All @@ -296,6 +300,8 @@ private String getContentType(@Nullable Integer category, List<String> tags) {
return RULETEMPLATES_CONTENT_TYPE;
} else if (UIWIDGETS_CATEGORY.equals(category)) {
return UIWIDGETS_CONTENT_TYPE;
} else if (BLOCKLIBRARIES_CATEGORY.equals(category)) {
return BLOCKLIBRARIES_CONTENT_TYPE;
} else if (BUNDLES_CATEGORY.equals(category)) {
if (tags.contains("kar")) {
return KAR_CONTENT_TYPE;
Expand Down

0 comments on commit f641d1d

Please sign in to comment.