-
-
Notifications
You must be signed in to change notification settings - Fork 429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce metadata for all add-ons #3050
Changes from all commits
ace610b
46b1425
52fb698
6877094
071f793
50e8ce3
1194a4c
2417225
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 <type>.<name></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 <type>-<name></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 |
---|---|---|
@@ -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 |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -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); | ||
} | ||
|
@@ -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()); | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What has happened to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally I planned to change it to |
||
xstream.alias("type", NodeValue.class); | ||
xstream.alias("config-description", ConfigDescription.class); | ||
xstream.alias("config-description-ref", NodeAttributes.class); | ||
xstream.alias("parameter", ConfigDescriptionParameter.class); | ||
|
@@ -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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for commenting code already merged, but thought it would be easiest for proper context. Should it be mentioned that it's not needed to use "openHAB" as author here? At least it seems none of the add-ons in the openhab-addons repository have this, and the skeleton script doesn't create it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be the right time to drop this field now altogether - we more or less only kept it for backward-compatibility, but as we are breaking this now anyhow, I think we could remove it from the XSD.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or are we using it for Marketplace entries, @ghys?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The marketplace add-on service will build the add-on with what Discourse returns as the author of the topic as the add-on author:
openhab-core/bundles/org.openhab.core.addon.marketplace/src/main/java/org/openhab/core/addon/marketplace/internal/community/CommunityMarketplaceAddonService.java
Lines 334 to 336 in 8d64ecf
and the Karaf add-on service will put the constant "openHAB" in that field (and set it as "verified"):
openhab-core/bundles/org.openhab.core.karaf/src/main/java/org/openhab/core/karaf/internal/KarafAddonService.java
Lines 133 to 134 in 8f9dafd
The author and the checkmark are displayed in the UI, notably in the information tables in each add-on page. For "verified" add-ons it's also displayed in the list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ghys, this means that this field is definitely deprecated then.
@J-N-K Would you agree that this is a good moment to remove it from the XSD then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes