Skip to content

Commit

Permalink
Introduce metadata for all add-ons (openhab#3050)
Browse files Browse the repository at this point in the history
* Introduce addon.xml

Signed-off-by: Jan N. Klug <[email protected]>
  • Loading branch information
J-N-K authored Jan 15, 2023
1 parent dd58477 commit 3d54ee5
Show file tree
Hide file tree
Showing 81 changed files with 1,251 additions and 1,357 deletions.
2 changes: 1 addition & 1 deletion bom/openhab-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.binding.xml</artifactId>
<artifactId>org.openhab.core.addon.xml</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.core.binding.xml</name>
<name>org.openhab.core.addon.xml</name>
<comment></comment>
<projects>
</projects>
Expand Down
File renamed without changes.
70 changes: 70 additions & 0 deletions bundles/org.openhab.core.addon.xml/addon-1.0.0.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
targetNamespace="https://openhab.org/schemas/addon/v1.0.0">

<xs:import namespace="https://openhab.org/schemas/config-description/v1.0.0"
schemaLocation="https://openhab.org/schemas/config-description-1.0.0.xsd"/>

<xs:element name="addon">
<xs:complexType>
<xs:sequence>
<xs:element name="type" type="addonType"/>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="author" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>The organization maintaining the add-on (e.g. openHAB). Individual developer names should be avoided.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="connection" type="connectionType" minOccurs="0"/>
<xs:element name="countries" type="countryType" minOccurs="0">
<xs:annotation>
<xs:documentation>Comma-separated list of two-letter ISO country codes.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="service-id" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>The ID (service.pid or component.name) of the main add-on service, which can be configured through OSGi configuration admin service. Should only be used in combination with a config description definition. The default value is &lt;type&gt;.&lt;name&gt;</xs:documentation>
</xs:annotation>
</xs:element>
<xs:choice minOccurs="0">
<xs:element name="config-description" type="config-description:configDescription"/>
<xs:element name="config-description-ref" type="config-description:configDescriptionRef"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" type="config-description:idRestrictionPattern" use="required">
<xs:annotation>
<xs:documentation>The id is used to construct the UID of this add-on to &lt;type&gt;-&lt;name&gt;</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>

<xs:simpleType name="addonType">
<xs:restriction base="xs:string">
<xs:enumeration value="automation"/>
<xs:enumeration value="binding"/>
<xs:enumeration value="misc"/>
<xs:enumeration value="persistence"/>
<xs:enumeration value="transformation"/>
<xs:enumeration value="ui"/>
<xs:enumeration value="voice"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="connectionType">
<xs:restriction base="xs:string">
<xs:enumeration value="local"/>
<xs:enumeration value="cloud"/>
<xs:enumeration value="cloudDiscovery"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="countryType">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z]{2}(,[a-z]{2})*"/>
</xs:restriction>
</xs:simpleType>

</xs:schema>
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<version>4.0.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.core.binding.xml</artifactId>
<artifactId>org.openhab.core.addon.xml</artifactId>

<name>openHAB Core :: Bundles :: Binding XML</name>
<name>openHAB Core :: Bundles :: Add-on XML</name>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.addon.xml.internal;

import java.net.URI;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionBuilder;
import org.openhab.core.config.xml.util.ConverterAttributeMapValidator;
import org.openhab.core.config.xml.util.GenericUnmarshaller;
import org.openhab.core.config.xml.util.NodeIterator;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;

/**
* The {@link AddonInfoConverter} is a concrete implementation of the {@code XStream} {@link Converter} interface used
* to convert add-on information within an XML document into a {@link AddonInfoXmlResult} object.
* This converter converts {@code addon} XML tags.
*
* @author Michael Grammling - Initial contribution
* @author Andre Fuechsel - Made author tag optional
* @author Jan N. Klug - Refactored to cover all add-ons
*/
@NonNullByDefault
public class AddonInfoConverter extends GenericUnmarshaller<AddonInfoXmlResult> {
private static final String CONFIG_DESCRIPTION_URI_PLACEHOLDER = "addonInfoConverter:placeHolder";
private final ConverterAttributeMapValidator attributeMapValidator;

public AddonInfoConverter() {
super(AddonInfoXmlResult.class);

attributeMapValidator = new ConverterAttributeMapValidator(Map.of("id", true, "schemaLocation", false));
}

private @Nullable ConfigDescription readConfigDescription(NodeIterator nodeIterator) {
Object nextNode = nodeIterator.next();

if (nextNode != null) {
if (nextNode instanceof ConfigDescription configDescription) {
return configDescription;
}

nodeIterator.revert();
}

return null;
}

@Override
public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
// read attributes
Map<String, String> attributes = attributeMapValidator.readValidatedAttributes(reader);

String id = requireNonEmpty(attributes.get("id"), "Add-on id attribute is null or empty");

// set automatically extracted URI for a possible 'config-description' section
context.put("config-description.uri", CONFIG_DESCRIPTION_URI_PLACEHOLDER);

// read values
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
NodeIterator nodeIterator = new NodeIterator(nodes);

String type = requireNonEmpty((String) nodeIterator.nextValue("type", true), "Add-on type is null or empty");

String name = requireNonEmpty((String) nodeIterator.nextValue("name", true),
"Add-on name attribute is null or empty");
String description = requireNonEmpty((String) nodeIterator.nextValue("description", true),
"Add-on description is null or empty");

AddonInfo.Builder addonInfo = AddonInfo.builder(id, type).withName(name).withDescription(description);
addonInfo.withAuthor((String) nodeIterator.nextValue("author", false));
addonInfo.withConnection((String) nodeIterator.nextValue("connection", false));

addonInfo.withServiceId((String) nodeIterator.nextValue("service-id", false));

String configDescriptionURI = nodeIterator.nextAttribute("config-description-ref", "uri", false);
ConfigDescription configDescription = null;
if (configDescriptionURI == null) {
configDescription = readConfigDescription(nodeIterator);
if (configDescription != null) {
configDescriptionURI = configDescription.getUID().toString();
// if config description is missing the URI, recreate it with correct URI
if (CONFIG_DESCRIPTION_URI_PLACEHOLDER.equals(configDescriptionURI)) {
configDescriptionURI = type + ":" + id;
configDescription = ConfigDescriptionBuilder.create(URI.create(configDescriptionURI))
.withParameterGroups(configDescription.getParameterGroups())
.withParameters(configDescription.getParameters()).build();
}
}
}
addonInfo.withConfigDescriptionURI(configDescriptionURI);

nodeIterator.assertEndOfType();

// create object
return new AddonInfoXmlResult(addonInfo.build(), configDescription);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.xml.internal;
package org.openhab.core.addon.xml.internal;

import java.util.List;

Expand All @@ -33,23 +33,24 @@
import com.thoughtworks.xstream.XStream;

/**
* The {@link BindingInfoReader} reads XML documents, which contain the {@code binding} XML tag,
* and converts them to {@link BindingInfoXmlResult} objects.
* The {@link AddonInfoReader} reads XML documents, which contain the {@code binding} XML tag,
* and converts them to {@link AddonInfoXmlResult} objects.
* <p>
* This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document.
*
* @author Michael Grammling - Initial contribution
* @author Alex Tugarev - Extended by options and filter criteria
* @author Chris Jackson - Add parameter groups
* @author Jan N. Klug - Refactored to cover all add-ons
*/
@NonNullByDefault
public class BindingInfoReader extends XmlDocumentReader<BindingInfoXmlResult> {
public class AddonInfoReader extends XmlDocumentReader<AddonInfoXmlResult> {

/**
* The default constructor of this class.
*/
public BindingInfoReader() {
ClassLoader classLoader = BindingInfoReader.class.getClassLoader();
public AddonInfoReader() {
ClassLoader classLoader = AddonInfoReader.class.getClassLoader();
if (classLoader != null) {
super.setClassLoader(classLoader);
}
Expand All @@ -59,7 +60,7 @@ public BindingInfoReader() {
protected void registerConverters(XStream xstream) {
xstream.registerConverter(new NodeAttributesConverter());
xstream.registerConverter(new NodeValueConverter());
xstream.registerConverter(new BindingInfoConverter());
xstream.registerConverter(new AddonInfoConverter());
xstream.registerConverter(new ConfigDescriptionConverter());
xstream.registerConverter(new ConfigDescriptionParameterConverter());
xstream.registerConverter(new ConfigDescriptionParameterGroupConverter());
Expand All @@ -68,11 +69,11 @@ protected void registerConverters(XStream xstream) {

@Override
protected void registerAliases(XStream xstream) {
xstream.alias("binding", BindingInfoXmlResult.class);
xstream.alias("addon", AddonInfoXmlResult.class);
xstream.alias("name", NodeValue.class);
xstream.alias("description", NodeValue.class);
xstream.alias("author", NodeValue.class);
xstream.alias("service-id", NodeValue.class);
xstream.alias("type", NodeValue.class);
xstream.alias("config-description", ConfigDescription.class);
xstream.alias("config-description-ref", NodeAttributes.class);
xstream.alias("parameter", ConfigDescriptionParameter.class);
Expand All @@ -81,5 +82,6 @@ protected void registerAliases(XStream xstream) {
xstream.alias("option", NodeValue.class);
xstream.alias("filter", List.class);
xstream.alias("criteria", FilterCriteria.class);
xstream.alias("service-id", NodeValue.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.xml.internal;
package org.openhab.core.addon.xml.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoProvider;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.xml.AbstractXmlConfigDescriptionProvider;
import org.openhab.core.config.xml.osgi.XmlDocumentProvider;
Expand All @@ -23,52 +21,51 @@
import org.slf4j.LoggerFactory;

/**
* The {@link BindingInfoXmlProvider} is responsible managing any created
* objects by a {@link BindingInfoReader} for a certain bundle.
* The {@link AddonInfoXmlProvider} is responsible managing any created
* objects by a {@link AddonInfoReader} for a certain bundle.
* <p>
* This implementation registers each {@link BindingInfo} object at the {@link XmlBindingInfoProvider} which is itself
* registered as {@link BindingInfoProvider} service at the <i>OSGi</i> service registry.
* This implementation registers each {@link AddonInfo} object at the {@link XmlAddonInfoProvider} which is itself
* registered as {@link AddonInfoProvider} service at the <i>OSGi</i> service registry.
* <p>
* If there is a {@link ConfigDescription} object within the {@link BindingInfoXmlResult} object, it is added to the
* If there is a {@link ConfigDescription} object within the {@link AddonInfoXmlResult} object, it is added to the
* {@link AbstractXmlConfigDescriptionProvider} which is itself registered as <i>OSGi</i> service at the service
* registry.
*
* @author Michael Grammling - Initial contribution
*
* @see BindingInfoXmlProviderFactory
* @author Jan N. Klug - Refactored to cover all add-ons
*/
@NonNullByDefault
public class BindingInfoXmlProvider implements XmlDocumentProvider<BindingInfoXmlResult> {
public class AddonInfoXmlProvider implements XmlDocumentProvider<AddonInfoXmlResult> {

private Logger logger = LoggerFactory.getLogger(BindingInfoXmlProvider.class);
private Logger logger = LoggerFactory.getLogger(AddonInfoXmlProvider.class);

private final Bundle bundle;

private final XmlBindingInfoProvider bindingInfoProvider;
private final XmlAddonInfoProvider addonInfoProvider;
private final AbstractXmlConfigDescriptionProvider configDescriptionProvider;

public BindingInfoXmlProvider(Bundle bundle, XmlBindingInfoProvider bindingInfoProvider,
public AddonInfoXmlProvider(Bundle bundle, XmlAddonInfoProvider addonInfoProvider,
AbstractXmlConfigDescriptionProvider configDescriptionProvider) throws IllegalArgumentException {
if (bundle == null) {
throw new IllegalArgumentException("The Bundle must not be null!");
}

if (bindingInfoProvider == null) {
throw new IllegalArgumentException("The XmlBindingInfoProvider must not be null!");
if (addonInfoProvider == null) {
throw new IllegalArgumentException("The XmlAddonInfoProvider must not be null!");
}

if (configDescriptionProvider == null) {
throw new IllegalArgumentException("The XmlConfigDescriptionProvider must not be null!");
}

this.bundle = bundle;
this.bindingInfoProvider = bindingInfoProvider;
this.addonInfoProvider = addonInfoProvider;
this.configDescriptionProvider = configDescriptionProvider;
}

@Override
public synchronized void addingObject(BindingInfoXmlResult bindingInfoXmlResult) {
ConfigDescription configDescription = bindingInfoXmlResult.getConfigDescription();
public synchronized void addingObject(AddonInfoXmlResult addonInfoXmlResult) {
ConfigDescription configDescription = addonInfoXmlResult.configDescription();

if (configDescription != null) {
try {
Expand All @@ -78,7 +75,7 @@ public synchronized void addingObject(BindingInfoXmlResult bindingInfoXmlResult)
}
}

bindingInfoProvider.add(bundle, bindingInfoXmlResult.getBindingInfo());
addonInfoProvider.add(bundle, addonInfoXmlResult.addonInfo());
}

@Override
Expand All @@ -88,7 +85,7 @@ public void addingFinished() {

@Override
public synchronized void release() {
this.bindingInfoProvider.removeAll(bundle);
this.addonInfoProvider.removeAll(bundle);
this.configDescriptionProvider.removeAll(bundle);
}
}
Loading

0 comments on commit 3d54ee5

Please sign in to comment.