The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at http://www.eclipse.org.
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/build.properties b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/build.properties
new file mode 100644
index 00000000000..43a0adc299f
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/build.properties
@@ -0,0 +1,6 @@
+output.. = target/classes
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ about.html
+source.. = src/main/java/
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/pom.xml b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/pom.xml
new file mode 100644
index 00000000000..9f163ad4087
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.smarthome.extensionservice
+ pom
+ 0.9.0-SNAPSHOT
+
+
+ org.eclipse.smarthome.extensionservice.marketplace.automation
+ eclipse-plugin
+
+ Eclipse SmartHome IoT Marketplace Extension Automation Support
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/AutomationExtensionHandler.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/AutomationExtensionHandler.java
new file mode 100644
index 00000000000..e55ea003924
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/AutomationExtensionHandler.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.automation.internal;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.commons.io.IOUtils;
+import org.eclipse.smarthome.automation.template.RuleTemplateProvider;
+import org.eclipse.smarthome.core.storage.Storage;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtension;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtensionHandler;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceHandlerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link MarketplaceExtensionHandler} implementation, which handles rule templates as JSON files and installs
+ * them by adding them to a {@link Storage}. The templates are then served from this storage through a dedicated
+ * {@link RuleTemplateProvider}.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class AutomationExtensionHandler implements MarketplaceExtensionHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(AutomationExtensionHandler.class);
+
+ private MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider;
+
+ protected void setMarketplaceRuleTemplateProvider(MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider) {
+ this.marketplaceRuleTemplateProvider = marketplaceRuleTemplateProvider;
+ }
+
+ protected void unsetMarketplaceRuleTemplateProvider(
+ MarketplaceRuleTemplateProvider marketplaceRuleTemplateProvider) {
+ this.marketplaceRuleTemplateProvider = null;
+ }
+
+ @Override
+ public boolean supports(MarketplaceExtension ext) {
+ // we support only rule templates in JSON format so far
+ return ext.getType().equals(MarketplaceExtension.EXT_TYPE_RULE_TEMPLATE)
+ && ext.getPackageFormat().equals(MarketplaceExtension.EXT_FORMAT_JSON);
+ }
+
+ @Override
+ public boolean isInstalled(MarketplaceExtension ext) {
+ return marketplaceRuleTemplateProvider.get(ext.getId()) != null;
+ }
+
+ @Override
+ public void install(MarketplaceExtension ext) throws MarketplaceHandlerException {
+ String url = ext.getDownloadUrl();
+ try {
+ String template = getTemplate(url);
+ marketplaceRuleTemplateProvider.addTemplateAsJSON(ext.getId(), template);
+ } catch (IOException e) {
+ logger.error("Rule template from marketplace cannot be downloaded: {}", e.getMessage());
+ throw new MarketplaceHandlerException("Template cannot be downloaded.");
+ } catch (Exception e) {
+ logger.error("Rule template from marketplace is invalid: {}", e.getMessage());
+ throw new MarketplaceHandlerException("Template is not valid.");
+ }
+ }
+
+ @Override
+ public void uninstall(MarketplaceExtension ext) throws MarketplaceHandlerException {
+ marketplaceRuleTemplateProvider.remove(ext.getId());
+ }
+
+ private String getTemplate(String urlString) throws IOException {
+ URL url = new URL(urlString);
+ return IOUtils.toString(url);
+ }
+
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/MarketplaceRuleTemplateProvider.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/MarketplaceRuleTemplateProvider.java
new file mode 100644
index 00000000000..0ddf6368462
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/automation/internal/MarketplaceRuleTemplateProvider.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.automation.internal;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.eclipse.smarthome.automation.parser.Parser;
+import org.eclipse.smarthome.automation.parser.ParsingException;
+import org.eclipse.smarthome.automation.template.RuleTemplate;
+import org.eclipse.smarthome.automation.template.RuleTemplateProvider;
+import org.eclipse.smarthome.core.common.registry.DefaultAbstractManagedProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a {@link RuleTemplateProvider}, which gets its content from the marketplace extension service
+ * and stores it through the ESH storage service.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceRuleTemplateProvider extends DefaultAbstractManagedProvider
+ implements RuleTemplateProvider {
+
+ private final Logger logger = LoggerFactory.getLogger(MarketplaceRuleTemplateProvider.class);
+
+ private Parser parser;
+
+ @Override
+ public RuleTemplate getTemplate(String uid, Locale locale) {
+ return get(uid);
+ }
+
+ @Override
+ public Collection getTemplates(Locale locale) {
+ return getAll();
+ }
+
+ @Override
+ protected String getKey(RuleTemplate element) {
+ return element.getUID();
+ }
+
+ @Override
+ protected String getStorageName() {
+ return "org.eclipse.smarthome.extensionservice.marketplace.RuleTemplates";
+ }
+
+ @Override
+ protected String keyToString(String key) {
+ return key;
+ }
+
+ protected void setParser(Parser parser) {
+ this.parser = parser;
+ }
+
+ protected void unsetParser(Parser parser) {
+ this.parser = null;
+ }
+
+ /**
+ * This adds a new rule template to the persistent storage.
+ *
+ * @param uid the UID to be used for the template
+ * @param json the template content as a json string
+ *
+ * @throws ParsingException if the content cannot be parsed correctly
+ */
+ public void addTemplateAsJSON(String uid, String json) throws ParsingException {
+ try (InputStreamReader isr = new InputStreamReader(IOUtils.toInputStream(json))) {
+ Set templates = parser.parse(isr);
+ if (templates.size() != 1) {
+ throw new IllegalArgumentException("JSON must contain exactly one template!");
+ } else {
+ RuleTemplate entry = templates.iterator().next();
+ RuleTemplate template = new RuleTemplate(uid, entry.getLabel(), entry.getDescription(), entry.getTags(),
+ entry.getTriggers(), entry.getConditions(), entry.getActions(),
+ entry.getConfigurationDescriptions(), entry.getVisibility());
+ add(template);
+ }
+ } catch (IOException e) {
+ logger.error("Cannot close input stream.", e);
+ }
+ }
+
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.classpath b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.classpath
new file mode 100644
index 00000000000..7f457fa4138
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.project b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.project
new file mode 100644
index 00000000000..c707744c6b1
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.project
@@ -0,0 +1,33 @@
+
+
+ org.eclipse.smarthome.extensionservice.marketplace
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.ds.core.builder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.settings/org.eclipse.jdt.core.prefs b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..0c68a61dca8
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/ESH-INF/config/config.xml b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/ESH-INF/config/config.xml
new file mode 100644
index 00000000000..65b5f1a19df
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/ESH-INF/config/config.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ Whether to make bindings from the marketplace available or not.
+ true
+
+
+
+ Whether to make rule templates from the marketplace available or not.
+ true
+
+
+
+ Only show entries that have a certain maturity.
+ 1
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/META-INF/MANIFEST.MF b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..9d7e526315c
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/META-INF/MANIFEST.MF
@@ -0,0 +1,23 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Eclipse SmartHome IoT Marketplace Extension Service
+Bundle-SymbolicName: org.eclipse.smarthome.extensionservice.marketplace;singleton:=true
+Bundle-Vendor: Eclipse.org/SmartHome
+Bundle-Version: 0.9.0.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ClassPath: .
+Import-Package: com.thoughtworks.xstream,
+ org.apache.commons.io,
+ org.apache.commons.lang,
+ org.eclipse.smarthome.config.core,
+ org.eclipse.smarthome.config.xml.util,
+ org.eclipse.smarthome.core.common,
+ org.eclipse.smarthome.core.common.registry,
+ org.eclipse.smarthome.core.events,
+ org.eclipse.smarthome.core.extension,
+ org.eclipse.smarthome.core.storage,
+ org.osgi.framework,
+ org.slf4j
+Bundle-ActivationPolicy: lazy
+Service-Component: OSGI-INF/*.xml
+Export-Package: org.eclipse.smarthome.extensionservice.marketplace
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceBindingExtension.xml b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceBindingExtension.xml
new file mode 100644
index 00000000000..913b00df0c1
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceBindingExtension.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceExtensionService.xml b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceExtensionService.xml
new file mode 100644
index 00000000000..a559902bffe
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/OSGI-INF/MarketplaceExtensionService.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/about.html b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/about.html
new file mode 100644
index 00000000000..1b9e722c419
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/about.html
@@ -0,0 +1,28 @@
+
+
+
+
+About
+
+
+
About This Content
+
+
<May 12, 2015>
+
License
+
+
The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at http://www.eclipse.org.
+
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/build.properties b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/build.properties
new file mode 100644
index 00000000000..831dfb342a0
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/build.properties
@@ -0,0 +1,7 @@
+output.. = target/classes
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ about.html,\
+ ESH-INF/
+source.. = src/main/java/
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/pom.xml b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/pom.xml
new file mode 100644
index 00000000000..eb7b4b846c9
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.smarthome.extensionservice
+ pom
+ 0.9.0-SNAPSHOT
+
+
+ org.eclipse.smarthome.extensionservice.marketplace
+ eclipse-plugin
+
+ Eclipse SmartHome IoT Marketplace Extension Service
+
+
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtension.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtension.java
new file mode 100644
index 00000000000..4472e189200
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtension.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace;
+
+import org.eclipse.smarthome.core.extension.Extension;
+
+/**
+ * This is an {@link Extension}, which additionally holds the package format and a download url for its content.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceExtension extends Extension {
+
+ // constants used to construct extension IDs
+ public static final String EXT_PREFIX = "market:";
+
+ // extension types from marketplace
+ public static final String EXT_TYPE_RULE_TEMPLATE = "ruletemplate";
+ public static final String EXT_TYPE_BINDING = "binding";
+
+ // extension package formats from marketplace
+ public static final String EXT_FORMAT_BUNDLE = "bundle";
+ public static final String EXT_FORMAT_JSON = "json";
+
+ // we mark them as transient, so that they are not serialized through GSON
+ private transient String downloadUrl;
+ private transient String packageFormat;
+
+ public MarketplaceExtension(String id, String type, String label, String version, String link, boolean installed,
+ String description, String backgroundColor, String imageLink, String downloadUrl, String packageFormat) {
+ super(id, type, label, version, link, installed, description, backgroundColor, imageLink);
+ this.downloadUrl = downloadUrl;
+ this.packageFormat = packageFormat;
+ }
+
+ /**
+ * returns the download url for the content of this extension
+ *
+ * @return a download url string
+ */
+ public String getDownloadUrl() {
+ return downloadUrl;
+ }
+
+ /**
+ * returns the package format of this extension
+ *
+ * @return package format of the extension
+ */
+ public String getPackageFormat() {
+ return packageFormat;
+ }
+
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtensionHandler.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtensionHandler.java
new file mode 100644
index 00000000000..89179877378
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceExtensionHandler.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace;
+
+/**
+ * This interface can be implemented by services that want to register as handlers for specific marketplace extension
+ * types and formats.
+ * In a system there should always only be exactly one handler responsible for a given type+format combination. If
+ * multiple handers support it, it is undefined which one will be called.
+ * This mechanism allows solutions to add support for specific formats (e.g. Karaf features) that are not supported by
+ * ESH out of the box.
+ * It also allows to decide which extension types are made available at all.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public interface MarketplaceExtensionHandler {
+
+ /**
+ * Tells whether this handler supports a given extension.
+ *
+ * @param ext the extension in question
+ * @return true, if the extension is supported, false otherwise
+ */
+ boolean supports(MarketplaceExtension ext);
+
+ /**
+ * Tells whether a given extension is currently installed.
+ * Note: This method is only called, if the hander claimed support for the extension before.
+ *
+ * @param ext the extension in question
+ * @return true, if the extension is installed, false otherwise
+ */
+ boolean isInstalled(MarketplaceExtension ext);
+
+ /**
+ * Installs a given extension.
+ * Note: This method is only called, if the hander claimed support for the extension before.
+ *
+ * @param ext the extension to install
+ * @throws MarketplaceHandlerException if the installation failed for some reason
+ */
+ void install(MarketplaceExtension ext) throws MarketplaceHandlerException;
+
+ /**
+ * Uninstalls a given extension.
+ * Note: This method is only called, if the hander claimed support for the extension before.
+ *
+ * @param ext the extension to uninstall
+ * @throws MarketplaceHandlerException if the uninstallation failed for some reason
+ */
+ void uninstall(MarketplaceExtension ext) throws MarketplaceHandlerException;
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceHandlerException.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceHandlerException.java
new file mode 100644
index 00000000000..70083ae7bb7
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/MarketplaceHandlerException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace;
+
+/**
+ * This is an exception that can be thrown by {@link MarketplaceExtensionHandler}s if some operation fails.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceHandlerException extends Exception {
+
+ private static final long serialVersionUID = -5652014141471618161L;
+
+ /**
+ * Main constructor
+ *
+ * @param message A message describing the issue
+ */
+ public MarketplaceHandlerException(String message) {
+ super(message);
+ }
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/BindingExtensionHandler.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/BindingExtensionHandler.java
new file mode 100644
index 00000000000..41ca5d64c01
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/BindingExtensionHandler.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.LineIterator;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtension;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtensionHandler;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceHandlerException;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link MarketplaceExtensionHandler} implementation, which handles bindings as jar files (OSGi bundles) and installs
+ * them through the standard OSGi bundle installation mechanism.
+ * The information, which installed bundle corresponds to which extension is written to a file in the bundle's data
+ * store. It is therefore wiped together with the bundles upon an OSGi "clean".
+ * We might want to move this class into a separate bundle in future, when we add support for further extension types.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class BindingExtensionHandler implements MarketplaceExtensionHandler {
+
+ private static final String BINDING_FILE = "installedBindingsMap.csv";
+
+ private final Logger logger = LoggerFactory.getLogger(BindingExtensionHandler.class);
+
+ private Map installedBindings;
+
+ private BundleContext bundleContext;
+
+ protected void activate(BundleContext bundleContext, Map config) {
+ this.bundleContext = bundleContext;
+ installedBindings = loadInstalledBindingsMap();
+ }
+
+ protected void deactivate() {
+ this.installedBindings = null;
+ this.bundleContext = null;
+ }
+
+ @Override
+ public boolean supports(MarketplaceExtension ext) {
+ // we support only bindings as pure OSGi bundles
+ return ext.getType().equals(MarketplaceExtension.EXT_TYPE_BINDING)
+ && ext.getPackageFormat().equals(MarketplaceExtension.EXT_FORMAT_BUNDLE);
+ }
+
+ @Override
+ public boolean isInstalled(MarketplaceExtension ext) {
+ return installedBindings.containsKey(ext.getId());
+ }
+
+ @Override
+ public void install(MarketplaceExtension ext) throws MarketplaceHandlerException {
+ String url = ext.getDownloadUrl();
+ try {
+ Bundle bundle = bundleContext.installBundle(url);
+ try {
+ bundle.start();
+ } catch (BundleException e) {
+ logger.warn("Installed bundle, but failed to start it: {}", e.getMessage());
+ }
+ installedBindings.put(ext.getId(), bundle.getBundleId());
+ persistInstalledBindingsMap(installedBindings);
+ } catch (BundleException e) {
+ throw new MarketplaceHandlerException("Binding cannot be installed: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void uninstall(MarketplaceExtension ext) throws MarketplaceHandlerException {
+ Long id = installedBindings.get(ext.getId());
+ if (id != null) {
+ Bundle bundle = bundleContext.getBundle(id);
+ if (bundle != null) {
+ try {
+ bundle.stop();
+ bundle.uninstall();
+ installedBindings.remove(ext.getId());
+ persistInstalledBindingsMap(installedBindings);
+ } catch (BundleException e) {
+ throw new MarketplaceHandlerException("Failed deinstalling binding: " + e.getMessage());
+ }
+ } else {
+ // we do not have such a bundle, so let's remove it from our internal map
+ installedBindings.remove(ext.getId());
+ persistInstalledBindingsMap(installedBindings);
+ throw new MarketplaceHandlerException("Id not known.");
+ }
+ } else {
+ throw new MarketplaceHandlerException("Id not known.");
+ }
+ }
+
+ private Map loadInstalledBindingsMap() {
+ File dataFile = bundleContext.getDataFile(BINDING_FILE);
+ if (dataFile != null && dataFile.exists()) {
+ try (FileReader reader = new FileReader(dataFile)) {
+ LineIterator lineIterator = IOUtils.lineIterator(reader);
+ Map map = new HashMap<>();
+ while (lineIterator.hasNext()) {
+ String line = lineIterator.nextLine();
+ String[] parts = line.split(";");
+ if (parts.length == 2) {
+ try {
+ map.put(parts[0], Long.valueOf(parts[1]));
+ } catch (NumberFormatException e) {
+ logger.debug("Cannot parse '{}' as a number in file {} - ignoring it.", parts[1],
+ dataFile.getName());
+ }
+ } else {
+ logger.debug("Invalid line in file {} - ignoring it:\n{}", dataFile.getName(), line);
+ }
+ }
+ return map;
+ } catch (IOException e) {
+ logger.debug("File '{}' for installed bindings does not exist.", dataFile.getName());
+ // ignore and just return an empty map
+ }
+ }
+ return new HashMap<>();
+ }
+
+ private synchronized void persistInstalledBindingsMap(Map map) {
+ File dataFile = bundleContext.getDataFile(BINDING_FILE);
+ if (dataFile != null) {
+ try (FileWriter writer = new FileWriter(dataFile)) {
+ for (Entry entry : map.entrySet()) {
+ writer.write(entry.getKey() + ";" + entry.getValue() + System.lineSeparator());
+ }
+ } catch (IOException e) {
+ logger.warn("Failed writing file '{}': {}", dataFile.getName(), e.getMessage());
+ }
+ } else {
+ logger.debug("System does not support bundle data files -> not persisting installed binding info");
+ }
+ }
+
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceExtensionService.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceExtensionService.java
new file mode 100644
index 00000000000..761741127f6
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceExtensionService.java
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.eclipse.smarthome.core.events.Event;
+import org.eclipse.smarthome.core.events.EventPublisher;
+import org.eclipse.smarthome.core.extension.Extension;
+import org.eclipse.smarthome.core.extension.ExtensionEventFactory;
+import org.eclipse.smarthome.core.extension.ExtensionService;
+import org.eclipse.smarthome.core.extension.ExtensionType;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtension;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceExtensionHandler;
+import org.eclipse.smarthome.extensionservice.marketplace.MarketplaceHandlerException;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Node;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an {@link ExtensionService}, which accesses the Eclipse IoT Marketplace and makes its content available as
+ * extensions.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceExtensionService implements ExtensionService {
+
+ // constants used in marketplace nodes
+ private static final String MP_PACKAGETYPE_BINDING = "binding";
+ private static final String MP_PACKAGETYPE_RULE_TEMPLATE = "rule_template";
+
+ private final Logger logger = LoggerFactory.getLogger(MarketplaceExtensionService.class);
+
+ private MarketplaceProxy proxy;
+ private EventPublisher eventPublisher;
+ private Pattern labelPattern = Pattern.compile("<.*>"); // checks for the existence of any xml element
+ private Pattern descriptionPattern = Pattern.compile("<(javascript|div|font)"); // checks for the existence of some
+ // invalid elements
+
+ private boolean includeBindings = true;
+ private boolean includeRuleTemplates = true;
+ private int maturityLevel = 1;
+ private Set extensionHandlers = new HashSet<>();
+
+ protected void activate(Map config) {
+ this.proxy = new MarketplaceProxy();
+ modified(config);
+ }
+
+ protected void deactivate() {
+ this.proxy.dispose();
+ this.proxy = null;
+ }
+
+ protected void modified(Map config) {
+ Object bindingCfg = config.get("bindings");
+ if (bindingCfg != null) {
+ this.includeBindings = bindingCfg.toString().equals(Boolean.TRUE.toString());
+ }
+ Object ruleTemplateCfg = config.get("ruletemplates");
+ if (ruleTemplateCfg != null) {
+ this.includeRuleTemplates = ruleTemplateCfg.toString().equals(Boolean.TRUE.toString());
+ }
+ Object cfgMaturityLevel = config.get("maturity");
+ if (cfgMaturityLevel != null) {
+ try {
+ this.maturityLevel = Integer.valueOf(cfgMaturityLevel.toString());
+ } catch (NumberFormatException e) {
+ logger.warn("Ignoring invalid value '{}' for configuration parameter '{}'", cfgMaturityLevel.toString(),
+ "maturity");
+ }
+ }
+ }
+
+ protected void setEventPublisher(EventPublisher eventPublisher) {
+ this.eventPublisher = eventPublisher;
+ }
+
+ protected void unsetEventPublisher(EventPublisher eventPublisher) {
+ this.eventPublisher = null;
+ }
+
+ protected void addExtensionHandler(MarketplaceExtensionHandler handler) {
+ this.extensionHandlers.add(handler);
+ }
+
+ protected void removeExtensionHandler(MarketplaceExtensionHandler handler) {
+ this.extensionHandlers.remove(handler);
+ }
+
+ @Override
+ public List getExtensions(Locale locale) {
+ List nodes = proxy.getNodes();
+ List exts = new ArrayList<>(nodes.size());
+ for (Node node : nodes) {
+ if (node.id == null) {
+ // workaround for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=512493
+ continue;
+ }
+ if (toMaturityLevel(node.status) < this.maturityLevel) {
+ continue;
+ }
+ if (!includeBindings && node.packagetypes.equals(MP_PACKAGETYPE_BINDING)) {
+ continue;
+ }
+ if (!includeRuleTemplates && node.packagetypes.equals(MP_PACKAGETYPE_RULE_TEMPLATE)) {
+ continue;
+ }
+
+ MarketplaceExtension ext = convertToExtension(node);
+ if (ext != null) {
+ if (setInstalledFlag(ext)) {
+ exts.add(ext);
+ }
+ }
+ }
+ return exts;
+ }
+
+ private boolean setInstalledFlag(MarketplaceExtension ext) {
+ for (MarketplaceExtensionHandler handler : extensionHandlers) {
+ if (handler.supports(ext)) {
+ ext.setInstalled(handler.isInstalled(ext));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private MarketplaceExtension convertToExtension(Node node) {
+ String extId = getExtensionId(node);
+
+ String name = node.name;
+ String desc = node.shortdescription;
+ String version = StringUtils.isNotEmpty(node.version) ? node.version : "1.0";
+
+ if (!validName(name) || !validDescription(desc)) {
+ logger.debug("Ignoring node {} due to invalid content.", node.id);
+ return null;
+ }
+ if (MP_PACKAGETYPE_BINDING.equals(node.packagetypes)) {
+ MarketplaceExtension ext = new MarketplaceExtension(extId, MarketplaceExtension.EXT_TYPE_BINDING, name,
+ version, node.supporturl, false, desc, null, node.image, node.updateurl, node.packageformat);
+ return ext;
+ } else if (MP_PACKAGETYPE_RULE_TEMPLATE.equals(node.packagetypes)) {
+ MarketplaceExtension ext = new MarketplaceExtension(extId, MarketplaceExtension.EXT_TYPE_RULE_TEMPLATE,
+ name, version, node.supporturl, false, desc, null, node.image, node.updateurl, node.packageformat);
+ return ext;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Extension getExtension(String id, Locale locale) {
+ for (Extension extension : getExtensions(locale)) {
+ if (extension.getId().equals(id)) {
+ return extension;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List getTypes(Locale locale) {
+ ArrayList types = new ArrayList<>(2);
+ List exts = getExtensions(locale);
+ if (includeBindings) {
+ for (Extension ext : exts) {
+ if (ext.getType().equals(MarketplaceExtension.EXT_TYPE_BINDING)) {
+ types.add(new ExtensionType(MarketplaceExtension.EXT_TYPE_BINDING, "Bindings"));
+ break;
+ }
+ }
+ }
+ if (includeRuleTemplates) {
+ for (Extension ext : exts) {
+ if (ext.getType().equals(MarketplaceExtension.EXT_TYPE_RULE_TEMPLATE)) {
+ types.add(new ExtensionType(MarketplaceExtension.EXT_TYPE_RULE_TEMPLATE, "Rule Templates"));
+ break;
+ }
+ }
+ }
+ return Collections.unmodifiableList(types);
+ }
+
+ @Override
+ public void install(String extensionId) {
+ Extension ext = getExtension(extensionId, null);
+ if (ext instanceof MarketplaceExtension) {
+ MarketplaceExtension mpExt = (MarketplaceExtension) ext;
+ for (MarketplaceExtensionHandler handler : extensionHandlers) {
+ if (handler.supports(mpExt)) {
+ if (!handler.isInstalled(mpExt)) {
+ try {
+ handler.install(mpExt);
+ postInstalledEvent(extensionId);
+ } catch (MarketplaceHandlerException e) {
+ postFailureEvent(extensionId, e.getMessage());
+ }
+ } else {
+ postFailureEvent(extensionId, "Extension is already installed.");
+ }
+ return;
+ }
+ }
+ }
+ postFailureEvent(extensionId, "Extension not known.");
+ }
+
+ @Override
+ public void uninstall(String extensionId) {
+ Extension ext = getExtension(extensionId, null);
+ if (ext instanceof MarketplaceExtension) {
+ MarketplaceExtension mpExt = (MarketplaceExtension) ext;
+ for (MarketplaceExtensionHandler handler : extensionHandlers) {
+ if (handler.supports(mpExt)) {
+ if (handler.isInstalled(mpExt)) {
+ try {
+ handler.uninstall(mpExt);
+ postUninstalledEvent(extensionId);
+ } catch (MarketplaceHandlerException e) {
+ postFailureEvent(extensionId, e.getMessage());
+ }
+ } else {
+ postFailureEvent(extensionId, "Extension is not installed.");
+ }
+ return;
+ }
+ }
+ }
+ postFailureEvent(extensionId, "Extension not known.");
+ }
+
+ private void postInstalledEvent(String extensionId) {
+ Event event = ExtensionEventFactory.createExtensionInstalledEvent(extensionId);
+ eventPublisher.post(event);
+ }
+
+ private void postUninstalledEvent(String extensionId) {
+ Event event = ExtensionEventFactory.createExtensionUninstalledEvent(extensionId);
+ eventPublisher.post(event);
+ }
+
+ private void postFailureEvent(String extensionId, String msg) {
+ Event event = ExtensionEventFactory.createExtensionFailureEvent(extensionId, msg);
+ eventPublisher.post(event);
+ }
+
+ private String getExtensionId(Node node) {
+ StringBuilder sb = new StringBuilder(MarketplaceExtension.EXT_PREFIX);
+ switch (node.packagetypes) {
+ case MP_PACKAGETYPE_RULE_TEMPLATE:
+ sb.append(MarketplaceExtension.EXT_TYPE_RULE_TEMPLATE).append("-");
+ break;
+ case MP_PACKAGETYPE_BINDING:
+ sb.append(MarketplaceExtension.EXT_TYPE_BINDING).append("-");
+ break;
+ default:
+ return null;
+ }
+ sb.append(node.id.replaceAll("[^a-zA-Z0-9_]", ""));
+ return sb.toString();
+ }
+
+ private int toMaturityLevel(String maturity) {
+ switch (maturity) {
+ case "Alpha":
+ return 0;
+ case "Beta":
+ return 1;
+ case "Production/Stable":
+ return 2;
+ case "Mature":
+ return 3;
+ default:
+ logger.debug("Unknown maturity level value '{}' - using 'Alpha' instead.", maturity);
+ return 0;
+ }
+ }
+
+ private boolean validName(String name) {
+ return !labelPattern.matcher(name).find();
+ }
+
+ private boolean validDescription(String desc) {
+ return !descriptionPattern.matcher(desc).find();
+ }
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceProxy.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceProxy.java
new file mode 100644
index 00000000000..0a8288d2580
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceProxy.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.smarthome.config.xml.util.XmlDocumentReader;
+import org.eclipse.smarthome.core.common.ThreadPoolManager;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Marketplace;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Node;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class provides local access to the market place content. Once started, it downloads the catalog and then makes
+ * its content available from memory.
+ *
+ * Note that there is no progressive/lazy browsing implemented yet, but the service downloads the whole catalog.
+ * Once the marketplace is filled with a lot of content, this will need to be addressed.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceProxy {
+
+ private final Logger logger = LoggerFactory.getLogger(MarketplaceProxy.class);
+
+ private final static String MP_URL = "https://marketplace.eclipse.org/taxonomy/term/4988%2C4396/api/p?client=org.eclipse.smarthome";
+ private final URL url;
+ private Node[] cachedNodes = null;
+ private long refresh_interval = 3600;
+ private long retry_delay = 60;
+ private ScheduledExecutorService executorService;
+ private ScheduledFuture> refreshJob;
+
+ /**
+ * Creates a new instance, which immediately schedules a synchronization with the marketplace content.
+ */
+ public MarketplaceProxy() {
+ try {
+ url = new URL(MP_URL);
+ this.executorService = Executors.newSingleThreadScheduledExecutor();
+ this.refreshJob = this.executorService.scheduleWithFixedDelay(() -> refresh(), 0, refresh_interval,
+ TimeUnit.SECONDS);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Something is very wrong - cannot instantiate URL " + MP_URL);
+ }
+ }
+
+ /**
+ * returns the full list of marketplace nodes
+ *
+ * @return list of marketplace nodes
+ */
+ public synchronized List getNodes() {
+ if (cachedNodes == null) {
+ XmlDocumentReader reader = new MarketplaceXMLReader();
+ try {
+ Marketplace result = reader.readFromXML(url);
+ cachedNodes = result.categories[0].nodes;
+ } catch (Exception e) {
+ if (cachedNodes == null) {
+ logger.warn("Failed downloading Marketplace entries: {}", e.getMessage());
+ logger.warn("Retrying again in a minute");
+ ThreadPoolManager.getScheduledPool("marketplace").schedule(() -> refresh(), retry_delay,
+ TimeUnit.SECONDS);
+ } else {
+ logger.debug("Cannot access IoT Marketplace - will continue to use cached results: {}",
+ e.getMessage());
+ }
+ }
+ }
+ return Arrays.asList(cachedNodes);
+ }
+
+ /**
+ * Refreshes the local content by synchronizing with the remote marketplace.
+ */
+ public synchronized void refresh() {
+ cachedNodes = null;
+ getNodes();
+ }
+
+ public void dispose() {
+ if (this.refreshJob != null && !this.refreshJob.isCancelled()) {
+ this.refreshJob.cancel(true);
+ this.refreshJob = null;
+ }
+ this.executorService.shutdown();
+ }
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceXMLReader.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceXMLReader.java
new file mode 100644
index 00000000000..376b47b85c2
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/MarketplaceXMLReader.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal;
+
+import org.eclipse.smarthome.config.xml.util.XmlDocumentReader;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Category;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Marketplace;
+import org.eclipse.smarthome.extensionservice.marketplace.internal.model.Node;
+
+import com.thoughtworks.xstream.XStream;
+
+/**
+ * This is the XML reader for the marketplace content.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ *
+ */
+public class MarketplaceXMLReader extends XmlDocumentReader {
+
+ public MarketplaceXMLReader() {
+ super.setClassLoader(Marketplace.class.getClassLoader());
+ }
+
+ @Override
+ public void registerConverters(XStream xstream) {
+ }
+
+ @Override
+ public void registerAliases(XStream xstream) {
+ xstream.alias("marketplace", Marketplace.class);
+ xstream.addImplicitArray(Marketplace.class, "categories");
+ xstream.alias("category", Category.class);
+ xstream.addImplicitArray(Category.class, "nodes");
+ xstream.alias("node", Node.class);
+ xstream.aliasAttribute(Node.class, "id", "id");
+ xstream.aliasAttribute(Node.class, "name", "name");
+
+ // ignore what we do not know
+ xstream.ignoreUnknownElements();
+ }
+
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Category.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Category.java
new file mode 100644
index 00000000000..ffed480dfd2
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Category.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal.model;
+
+/**
+ * This is a category that holds the nodes as individual entries of the marketplace.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ */
+public class Category {
+
+ /**
+ * The category of the marketplace
+ */
+ public Node[] nodes;
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Marketplace.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Marketplace.java
new file mode 100644
index 00000000000..d92b2fc6726
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Marketplace.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal.model;
+
+/**
+ * This is the parent object that holds the category of the market.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ */
+public class Marketplace {
+
+ /**
+ * The category of the marketplace
+ */
+ public Category[] categories;
+}
diff --git a/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Node.java b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Node.java
new file mode 100644
index 00000000000..eec64b92f28
--- /dev/null
+++ b/extensions/extensionservice/org.eclipse.smarthome.extensionservice.marketplace/src/main/java/org/eclipse/smarthome/extensionservice/marketplace/internal/model/Node.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014-2017 by the respective copyright holders.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.smarthome.extensionservice.marketplace.internal.model;
+
+import java.util.Set;
+
+/**
+ * A node represents an entry on the marketplace.
+ *
+ * @author Kai Kreuzer - Initial contribution and API
+ */
+public class Node {
+
+ public String id;
+
+ public String name;
+
+ public Integer favorited;
+
+ public Integer installsTotal;
+
+ public Integer installsRecent;
+
+ public Set tags;
+
+ public String shortdescription;
+
+ public String body;
+
+ public Long created;
+
+ public Long changed;
+
+ public String image;
+
+ public String license;
+
+ public String companyname;
+
+ public String status;
+
+ public String version;
+
+ public String supporturl;
+
+ public String packagetypes;
+
+ public String packageformat;
+
+ public String updateurl;
+}
diff --git a/extensions/extensionservice/pom.xml b/extensions/extensionservice/pom.xml
new file mode 100644
index 00000000000..5f694f6815b
--- /dev/null
+++ b/extensions/extensionservice/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ org.eclipse.smarthome.extension
+ pom
+ 0.9.0-SNAPSHOT
+
+
+ 4.0.0
+ org.eclipse.smarthome.extensionservice
+ pom
+
+ Eclipse SmartHome Extension Service Extensions
+
+ pom
+
+
+ org.eclipse.smarthome.extensionservice.marketplace
+ org.eclipse.smarthome.extensionservice.marketplace.automation
+
+
+
diff --git a/extensions/pom.xml b/extensions/pom.xml
index b11e901abac..9a40a9281ce 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -17,6 +17,7 @@
binding
+ extensionserviceiotransformui
diff --git a/features/karaf/esh-ext/src/main/feature/feature.xml b/features/karaf/esh-ext/src/main/feature/feature.xml
index 663412bc619..1dd33a5e069 100644
--- a/features/karaf/esh-ext/src/main/feature/feature.xml
+++ b/features/karaf/esh-ext/src/main/feature/feature.xml
@@ -55,6 +55,18 @@
mvn:org.eclipse.smarthome.binding/org.eclipse.smarthome.binding.yahooweather/${project.version}
+
+ esh-base
+ mvn:org.eclipse.smarthome.extensionservice/org.eclipse.smarthome.extensionservice.marketplace/${project.version}
+
+
+
+ esh-base
+ esh-automation-api
+ esh-extensionservice-marketplace
+ mvn:org.eclipse.smarthome.extensionservice/org.eclipse.smarthome.extensionservice.marketplace.automation/${project.version}
+
+
esh-basemvn:org.eclipse.smarthome.transform/org.eclipse.smarthome.transform.exec/${project.version}
diff --git a/features/org.eclipse.smarthome.feature.runtime.core/feature.xml b/features/org.eclipse.smarthome.feature.runtime.core/feature.xml
index e35abb1adad..74c5e82442a 100644
--- a/features/org.eclipse.smarthome.feature.runtime.core/feature.xml
+++ b/features/org.eclipse.smarthome.feature.runtime.core/feature.xml
@@ -271,4 +271,17 @@
version="0.0.0"
unpack="false"/>
+
+
+