Introduce metadata for all add-ons (#3050)

* Introduce addon.xml

Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
J-N-K 2023-01-15 15:31:42 +01:00 committed by GitHub
parent dd584779db
commit 3d54ee54d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1250 additions and 1356 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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 org.openhab.core.config.xml.util.XmlDocumentReader;
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 class BindingInfoReader extends XmlDocumentReader<BindingInfoXmlResult> {
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 @@ public class BindingInfoReader extends XmlDocumentReader<BindingInfoXmlResult> {
@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);
@ -81,5 +82,6 @@ public class BindingInfoReader extends XmlDocumentReader<BindingInfoXmlResult> {
xstream.alias("option", NodeValue.class);
xstream.alias("filter", List.class);
xstream.alias("criteria", FilterCriteria.class);
xstream.alias("service-id", NodeValue.class);
}
}

View File

@ -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;
@ -23,38 +21,37 @@ import org.slf4j.Logger;
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) {
@ -62,13 +59,13 @@ public class BindingInfoXmlProvider implements XmlDocumentProvider<BindingInfoXm
}
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 {
@ -78,7 +75,7 @@ public class BindingInfoXmlProvider implements XmlDocumentProvider<BindingInfoXm
}
}
bindingInfoProvider.add(bundle, bindingInfoXmlResult.getBindingInfo());
addonInfoProvider.add(bundle, addonInfoXmlResult.addonInfo());
}
@Override
@ -88,7 +85,7 @@ public class BindingInfoXmlProvider implements XmlDocumentProvider<BindingInfoXm
@Override
public synchronized void release() {
this.bindingInfoProvider.removeAll(bundle);
this.addonInfoProvider.removeAll(bundle);
this.configDescriptionProvider.removeAll(bundle);
}
}

View File

@ -0,0 +1,31 @@
/**
* 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 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.ConfigDescriptionProvider;
/**
* The {@link AddonInfoXmlResult} is an intermediate XML conversion result object which
* contains a mandatory {@link AddonInfo} and an optional {@link ConfigDescription} object.
* <p>
* If a {@link ConfigDescription} object exists, it must be added to the according {@link ConfigDescriptionProvider}.
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public record AddonInfoXmlResult(AddonInfo addonInfo, @Nullable ConfigDescription configDescription) {
}

View File

@ -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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ConfigDescription;
@ -25,15 +25,16 @@ import org.osgi.service.component.annotations.Reference;
* Provides {@link ConfigDescription}s for bindings which are read from XML files.
*
* @author Simon Kaufmann - Initial contribution
* @author Jan N. Klug - Refactored to cover all add-ons
*/
@Component(service = ConfigDescriptionProvider.class, immediate = true, property = { "openhab.scope=core.xml.binding" })
@Component(service = ConfigDescriptionProvider.class, immediate = true, property = { "openhab.scope=core.xml.addon" })
@NonNullByDefault
public class BindingXmlConfigDescriptionProvider extends AbstractXmlConfigDescriptionProvider {
public class AddonXmlConfigDescriptionProvider extends AbstractXmlConfigDescriptionProvider {
private final ConfigI18nLocalizationService configI18nService;
@Activate
public BindingXmlConfigDescriptionProvider(final @Reference ConfigI18nLocalizationService configI18nService) {
public AddonXmlConfigDescriptionProvider(final @Reference ConfigI18nLocalizationService configI18nService) {
this.configI18nService = configI18nService;
}

View File

@ -0,0 +1,107 @@
/**
* 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.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.AddonI18nLocalizationService;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonInfoProvider;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.config.core.ConfigDescriptionProvider;
import org.openhab.core.config.xml.AbstractXmlBasedProvider;
import org.openhab.core.config.xml.AbstractXmlConfigDescriptionProvider;
import org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker;
import org.openhab.core.config.xml.osgi.XmlDocumentProvider;
import org.openhab.core.config.xml.osgi.XmlDocumentProviderFactory;
import org.openhab.core.config.xml.util.XmlDocumentReader;
import org.openhab.core.service.ReadyService;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link XmlAddonInfoProvider} is a concrete implementation of the {@link AddonInfoProvider} service interface.
* <p>
* This implementation manages any {@link AddonInfo} objects associated to specific modules. If a specific module
* disappears, any registered {@link AddonInfo} objects associated with that module are released.
*
* @author Michael Grammling - Initial contribution
* @author Michael Grammling - Refactoring: Provider/Registry pattern is used, added locale support
* @author Simon Kaufmann - factored out common aspects into {@link AbstractXmlBasedProvider}
* @author Jan N. Klug - Refactored to cover all add-ons
*/
@NonNullByDefault
@Component
public class XmlAddonInfoProvider extends AbstractXmlBasedProvider<String, AddonInfo>
implements AddonInfoProvider, XmlDocumentProviderFactory<AddonInfoXmlResult> {
private static final String XML_DIRECTORY = "/OH-INF/addon/";
public static final String READY_MARKER = "openhab.xmlAddonInfo";
private final AddonI18nLocalizationService addonI18nService;
private final AbstractXmlConfigDescriptionProvider configDescriptionProvider;
private final XmlDocumentBundleTracker<AddonInfoXmlResult> addonInfoTracker;
private final Future<?> trackerJob;
@Activate
public XmlAddonInfoProvider(final @Reference AddonI18nLocalizationService addonI18nService,
final @Reference(target = "(openhab.scope=core.xml.addon)") ConfigDescriptionProvider configDescriptionProvider,
final @Reference ReadyService readyService, ComponentContext componentContext) {
this.addonI18nService = addonI18nService;
this.configDescriptionProvider = (AbstractXmlConfigDescriptionProvider) configDescriptionProvider;
XmlDocumentReader<AddonInfoXmlResult> addonInfoReader = new AddonInfoReader();
addonInfoTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY,
addonInfoReader, this, READY_MARKER, readyService);
ScheduledExecutorService scheduler = ThreadPoolManager
.getScheduledPool(XmlDocumentBundleTracker.THREAD_POOL_NAME);
trackerJob = scheduler.submit(addonInfoTracker::open);
}
@Deactivate
public void deactivate() {
trackerJob.cancel(true);
addonInfoTracker.close();
}
@Override
public synchronized @Nullable AddonInfo getAddonInfo(@Nullable String id, @Nullable Locale locale) {
return id == null ? null : get(id, locale);
}
@Override
public synchronized Set<AddonInfo> getAddonInfos(@Nullable Locale locale) {
return new HashSet<>(getAll(locale));
}
@Override
protected @Nullable AddonInfo localize(Bundle bundle, AddonInfo bindingInfo, @Nullable Locale locale) {
return addonI18nService.createLocalizedAddonInfo(bundle, bindingInfo, locale);
}
@Override
public XmlDocumentProvider<AddonInfoXmlResult> createDocumentProvider(Bundle bundle) {
return new AddonInfoXmlProvider(bundle, this, configDescriptionProvider);
}
}

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
targetNamespace="https://openhab.org/schemas/binding/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="binding">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string" minOccurs="0"/>
<xs:element name="author" type="xs:string" minOccurs="0"/>
<xs:element name="service-id" type="xs:string" minOccurs="0"/>
<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:complexType>
</xs:element>
</xs:schema>

View File

@ -1,119 +0,0 @@
/**
* 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.binding.xml.internal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.config.core.ConfigDescription;
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.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
/**
* The {@link BindingInfoConverter} is a concrete implementation of the {@code XStream} {@link Converter} interface used
* to convert binding information within an XML document
* into a {@link BindingInfoXmlResult} object.
* <p>
* This converter converts {@code binding} XML tags.
*
* @author Michael Grammling - Initial contribution
* @author Andre Fuechsel - Made author tag optional
*/
@NonNullByDefault
public class BindingInfoConverter extends GenericUnmarshaller<BindingInfoXmlResult> {
private ConverterAttributeMapValidator attributeMapValidator;
public BindingInfoConverter() {
super(BindingInfoXmlResult.class);
attributeMapValidator = new ConverterAttributeMapValidator(
new String[][] { { "id", "true" }, { "schemaLocation", "false" } });
}
private @Nullable URI readConfigDescriptionURI(NodeIterator nodeIterator) throws ConversionException {
String uriText = nodeIterator.nextAttribute("config-description-ref", "uri", false);
if (uriText != null) {
try {
return new URI(uriText);
} catch (URISyntaxException ex) {
throw new ConversionException(
"The URI '" + uriText + "' in node " + "'config-description-ref' is invalid!", ex);
}
}
return null;
}
private @Nullable ConfigDescription readConfigDescription(NodeIterator nodeIterator) {
Object nextNode = nodeIterator.next();
if (nextNode != null) {
if (nextNode instanceof ConfigDescription) {
return (ConfigDescription) nextNode;
}
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"), "Binding id attribute is null or empty");
// set automatically extracted URI for a possible 'config-description' section
context.put("config-description.uri", "binding:" + id);
// read values
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
NodeIterator nodeIterator = new NodeIterator(nodes);
String name = requireNonEmpty((String) nodeIterator.nextValue("name", true),
"Binding name attribute is null or empty");
String description = (String) nodeIterator.nextValue("description", false);
String author = (String) nodeIterator.nextValue("author", false);
String serviceId = (String) nodeIterator.nextValue("service-id", false);
URI configDescriptionURI = readConfigDescriptionURI(nodeIterator);
ConfigDescription configDescription = null;
if (configDescriptionURI == null) {
configDescription = readConfigDescription(nodeIterator);
if (configDescription != null) {
configDescriptionURI = configDescription.getUID();
}
}
nodeIterator.assertEndOfType();
// create object
BindingInfo bindingInfo = new BindingInfo(id, name, description, author, serviceId, configDescriptionURI);
return new BindingInfoXmlResult(bindingInfo, configDescription);
}
}

View File

@ -1,52 +0,0 @@
/**
* 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.binding.xml.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionProvider;
/**
* The {@link BindingInfoXmlResult} is an intermediate XML conversion result object which
* contains a mandatory {@link BindingInfo} and an optional {@link ConfigDescription} object.
* <p>
* If a {@link ConfigDescription} object exists, it must be added to the according {@link ConfigDescriptionProvider}.
*
* @author Michael Grammling - Initial contribution
*/
@NonNullByDefault
public class BindingInfoXmlResult {
private BindingInfo bindingInfo;
private @Nullable ConfigDescription configDescription;
public BindingInfoXmlResult(BindingInfo bindingInfo, @Nullable ConfigDescription configDescription) {
this.bindingInfo = bindingInfo;
this.configDescription = configDescription;
}
public BindingInfo getBindingInfo() {
return bindingInfo;
}
public @Nullable ConfigDescription getConfigDescription() {
return configDescription;
}
@Override
public String toString() {
return "BindingInfoXmlResult [bindingInfo=" + bindingInfo + ", configDescription=" + configDescription + "]";
}
}

View File

@ -1,120 +0,0 @@
/**
* 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.binding.xml.internal;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoProvider;
import org.openhab.core.binding.i18n.BindingI18nLocalizationService;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.config.core.ConfigDescriptionProvider;
import org.openhab.core.config.xml.AbstractXmlBasedProvider;
import org.openhab.core.config.xml.AbstractXmlConfigDescriptionProvider;
import org.openhab.core.config.xml.osgi.XmlDocumentBundleTracker;
import org.openhab.core.config.xml.osgi.XmlDocumentProvider;
import org.openhab.core.config.xml.osgi.XmlDocumentProviderFactory;
import org.openhab.core.config.xml.util.XmlDocumentReader;
import org.openhab.core.service.ReadyService;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link XmlBindingInfoProvider} is a concrete implementation of the {@link BindingInfoProvider} service interface.
* <p>
* This implementation manages any {@link BindingInfo} objects associated to specific modules. If a specific module
* disappears, any registered {@link BindingInfo} objects associated with that module are released.
*
* @author Michael Grammling - Initial contribution
* @author Michael Grammling - Refactoring: Provider/Registry pattern is used, added locale support
* @author Simon Kaufmann - factored out common aspects into {@link AbstractXmlBasedProvider}
*/
@NonNullByDefault
@Component
public class XmlBindingInfoProvider extends AbstractXmlBasedProvider<String, BindingInfo>
implements BindingInfoProvider, XmlDocumentProviderFactory<BindingInfoXmlResult> {
private static final String XML_DIRECTORY = "/OH-INF/binding/";
public static final String READY_MARKER = "openhab.xmlBindingInfo";
private final BindingI18nLocalizationService bindingI18nService;
private AbstractXmlConfigDescriptionProvider configDescriptionProvider;
private @Nullable XmlDocumentBundleTracker<BindingInfoXmlResult> bindingInfoTracker;
private final ReadyService readyService;
private final ScheduledExecutorService scheduler = ThreadPoolManager
.getScheduledPool(XmlDocumentBundleTracker.THREAD_POOL_NAME);
private @Nullable Future<?> trackerJob;
@Activate
public XmlBindingInfoProvider(final @Reference BindingI18nLocalizationService bindingI18nService,
final @Reference(target = "(openhab.scope=core.xml.binding)") ConfigDescriptionProvider configDescriptionProvider,
final @Reference ReadyService readyService) {
this.bindingI18nService = bindingI18nService;
this.configDescriptionProvider = (AbstractXmlConfigDescriptionProvider) configDescriptionProvider;
this.readyService = readyService;
}
@Activate
public void activate(ComponentContext componentContext) {
XmlDocumentReader<BindingInfoXmlResult> bindingInfoReader = new BindingInfoReader();
bindingInfoTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY,
bindingInfoReader, this, READY_MARKER, readyService);
trackerJob = scheduler.submit(() -> {
bindingInfoTracker.open();
});
}
@Deactivate
public void deactivate(ComponentContext componentContext) {
Future<?> localTrackerJob = trackerJob;
if (localTrackerJob != null && !localTrackerJob.isDone()) {
localTrackerJob.cancel(true);
trackerJob = null;
}
XmlDocumentBundleTracker<BindingInfoXmlResult> localBindingInfoTracker = bindingInfoTracker;
if (localBindingInfoTracker != null) {
localBindingInfoTracker.close();
bindingInfoTracker = null;
}
}
@Override
public synchronized @Nullable BindingInfo getBindingInfo(@Nullable String id, @Nullable Locale locale) {
return id == null ? null : get(id, locale);
}
@Override
public synchronized Set<BindingInfo> getBindingInfos(@Nullable Locale locale) {
return new HashSet<>(getAll(locale));
}
@Override
protected @Nullable BindingInfo localize(Bundle bundle, BindingInfo bindingInfo, @Nullable Locale locale) {
return bindingI18nService.createLocalizedBindingInfo(bundle, bindingInfo, locale);
}
@Override
public XmlDocumentProvider<BindingInfoXmlResult> createDocumentProvider(Bundle bundle) {
return new BindingInfoXmlProvider(bundle, this, configDescriptionProvider);
}
}

View File

@ -12,20 +12,25 @@
*/
package org.openhab.core.io.rest.core.internal.addons;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.Collator;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Stream;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@ -41,10 +46,16 @@ import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.core.addon.Addon;
import org.openhab.core.addon.AddonEventFactory;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.addon.AddonService;
import org.openhab.core.addon.AddonType;
import org.openhab.core.auth.Role;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionRegistry;
import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.Event;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.io.rest.JSONResponse;
@ -52,6 +63,7 @@ import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.core.io.rest.Stream2JSONInputStream;
import org.openhab.core.io.rest.core.config.ConfigurationService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@ -105,13 +117,22 @@ public class AddonResource implements RESTResource {
private final Set<AddonService> addonServices = new CopyOnWriteArraySet<>();
private final EventPublisher eventPublisher;
private final LocaleService localeService;
private final ConfigurationService configurationService;
private final AddonInfoRegistry addonInfoRegistry;
private final ConfigDescriptionRegistry configDescriptionRegistry;
private @Context @NonNullByDefault({}) UriInfo uriInfo;
@Activate
public AddonResource(final @Reference EventPublisher eventPublisher, final @Reference LocaleService localeService) {
public AddonResource(final @Reference EventPublisher eventPublisher, final @Reference LocaleService localeService,
final @Reference ConfigurationService configurationService,
final @Reference AddonInfoRegistry addonInfoRegistry,
final @Reference ConfigDescriptionRegistry configDescriptionRegistry) {
this.eventPublisher = eventPublisher;
this.localeService = localeService;
this.configurationService = configurationService;
this.addonInfoRegistry = addonInfoRegistry;
this.configDescriptionRegistry = configDescriptionRegistry;
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@ -270,6 +291,80 @@ public class AddonResource implements RESTResource {
return Response.ok(null, MediaType.TEXT_PLAIN).build();
}
@GET
@Path("/{addonId: [a-zA-Z_0-9-:]+}/config")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "getAddonConfiguration", summary = "Get add-on configuration for given add-on ID.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "404", description = "Add-on does not exist"),
@ApiResponse(responseCode = "500", description = "Configuration can not be read due to internal error") })
public Response getConfiguration(final @PathParam("addonId") @Parameter(description = "addon ID") String addonId,
@QueryParam("serviceId") @Parameter(description = "service ID") @Nullable String serviceId) {
try {
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(addonId);
if (addonInfo == null) {
return Response.status(Status.NOT_FOUND).build();
}
Configuration configuration = configurationService.get(addonInfo.getServiceId());
return configuration != null ? Response.ok(configuration.getProperties()).build()
: Response.ok(Map.of()).build();
} catch (IOException e) {
logger.error("Cannot get configuration for service {}: {}", addonId, e.getMessage(), e);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
@PUT
@Path("/{addonId: [a-zA-Z_0-9-:]+}/config")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "updateAddonConfiguration", summary = "Updates an add-on configuration for given ID and returns the old configuration.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "204", description = "No old configuration"),
@ApiResponse(responseCode = "404", description = "Add-on does not exist"),
@ApiResponse(responseCode = "500", description = "Configuration can not be updated due to internal error") })
public Response updateConfiguration(@PathParam("addonId") @Parameter(description = "Add-on id") String addonId,
@QueryParam("serviceId") @Parameter(description = "service ID") @Nullable String serviceId,
@Nullable Map<String, Object> configuration) {
try {
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(addonId);
if (addonInfo == null) {
return Response.status(Status.NOT_FOUND).build();
}
Configuration oldConfiguration = configurationService.get(addonInfo.getServiceId());
configurationService.update(addonInfo.getServiceId(),
new Configuration(normalizeConfiguration(configuration, addonId)));
return oldConfiguration != null ? Response.ok(oldConfiguration.getProperties()).build()
: Response.noContent().build();
} catch (IOException ex) {
logger.error("Cannot update configuration for service {}: {}", addonId, ex.getMessage(), ex);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
private @Nullable Map<String, Object> normalizeConfiguration(@Nullable Map<String, Object> properties,
String addonId) {
if (properties == null || properties.isEmpty()) {
return properties;
}
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(addonId);
if (addonInfo == null || addonInfo.getConfigDescriptionURI() == null) {
return properties;
}
String configDescriptionURI = addonInfo.getConfigDescriptionURI();
if (configDescriptionURI != null) {
ConfigDescription configDesc = configDescriptionRegistry
.getConfigDescription(URI.create(configDescriptionURI));
if (configDesc != null) {
return ConfigUtil.normalizeTypes(properties, List.of(configDesc));
}
}
return properties;
}
private void postFailureEvent(String addonId, @Nullable String msg) {
Event event = AddonEventFactory.createAddonFailureEvent(addonId, msg);
eventPublisher.post(event);
@ -302,12 +397,7 @@ public class AddonResource implements RESTResource {
private Set<AddonType> getAddonTypesForService(AddonService addonService, Locale locale) {
final Collator coll = Collator.getInstance(locale);
coll.setStrength(Collator.PRIMARY);
Set<AddonType> ret = new TreeSet<>(new Comparator<AddonType>() {
@Override
public int compare(AddonType o1, AddonType o2) {
return coll.compare(o1.getLabel(), o2.getLabel());
}
});
Set<AddonType> ret = new TreeSet<>((o1, o2) -> coll.compare(o1.getLabel(), o2.getLabel()));
ret.addAll(addonService.getTypes(locale));
return ret;
}

View File

@ -1,216 +0,0 @@
/**
* 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.io.rest.core.internal.binding;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.auth.Role;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoRegistry;
import org.openhab.core.binding.dto.BindingInfoDTO;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionRegistry;
import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.rest.LocaleService;
import org.openhab.core.io.rest.RESTConstants;
import org.openhab.core.io.rest.RESTResource;
import org.openhab.core.io.rest.Stream2JSONInputStream;
import org.openhab.core.io.rest.core.config.ConfigurationService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* This class acts as a REST resource for bindings and is registered with the
* Jersey servlet.
*
* @author Dennis Nobel - Initial contribution
* @author Kai Kreuzer - refactored for using the OSGi JAX-RS connector
* @author Yordan Zhelev - Added Swagger annotations
* @author Franck Dechavanne - Added DTOs to ApiResponses
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
*/
@Component
@JaxrsResource
@JaxrsName(BindingResource.PATH_BINDINGS)
@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
@JSONRequired
@Path(BindingResource.PATH_BINDINGS)
@RolesAllowed({ Role.ADMIN })
@SecurityRequirement(name = "oauth2", scopes = { "admin" })
@Tag(name = BindingResource.PATH_BINDINGS)
@NonNullByDefault
public class BindingResource implements RESTResource {
/** The URI path to this resource */
public static final String PATH_BINDINGS = "bindings";
private final Logger logger = LoggerFactory.getLogger(BindingResource.class);
private final BindingInfoRegistry bindingInfoRegistry;
private final ConfigurationService configurationService;
private final ConfigDescriptionRegistry configDescRegistry;
private final LocaleService localeService;
@Activate
public BindingResource( //
final @Reference BindingInfoRegistry bindingInfoRegistry,
final @Reference ConfigurationService configurationService,
final @Reference ConfigDescriptionRegistry configDescRegistry,
final @Reference LocaleService localeService) {
this.bindingInfoRegistry = bindingInfoRegistry;
this.configurationService = configurationService;
this.configDescRegistry = configDescRegistry;
this.localeService = localeService;
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "getBindings", summary = "Get all bindings.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = BindingInfoDTO.class), uniqueItems = true))) })
public Response getAll(
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) {
final Locale locale = localeService.getLocale(language);
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos(locale);
return Response.ok(new Stream2JSONInputStream(bindingInfos.stream().map(b -> map(b, locale)))).build();
}
@GET
@Path("/{bindingId}/config")
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "getBindingConfiguration", summary = "Get binding configuration for given binding ID.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "404", description = "Binding does not exist"),
@ApiResponse(responseCode = "500", description = "Configuration can not be read due to internal error") })
public Response getConfiguration(@PathParam("bindingId") @Parameter(description = "service ID") String bindingId) {
try {
String configId = getConfigId(bindingId);
if (configId == null) {
logger.warn("Cannot get config id for binding id '{}', probably because binding does not exist.",
bindingId);
return Response.status(404).build();
}
Configuration configuration = configurationService.get(configId);
return configuration != null ? Response.ok(configuration.getProperties()).build()
: Response.ok(Collections.emptyMap()).build();
} catch (IOException ex) {
logger.error("Cannot get configuration for service {}: {}", bindingId, ex.getMessage(), ex);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
@PUT
@Path("/{bindingId}/config")
@Consumes(MediaType.APPLICATION_JSON)
@Produces({ MediaType.APPLICATION_JSON })
@Operation(operationId = "updateBindingConfiguration", summary = "Updates a binding configuration for given binding ID and returns the old configuration.", responses = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "204", description = "No old configuration"),
@ApiResponse(responseCode = "404", description = "Binding does not exist"),
@ApiResponse(responseCode = "500", description = "Configuration can not be updated due to internal error") })
public Response updateConfiguration(@PathParam("bindingId") @Parameter(description = "service ID") String bindingId,
@Nullable Map<String, Object> configuration) {
try {
String configId = getConfigId(bindingId);
if (configId == null) {
logger.warn("Cannot get config id for binding id '{}', probably because binding does not exist.",
bindingId);
return Response.status(404).build();
}
Configuration oldConfiguration = configurationService.get(configId);
configurationService.update(configId, new Configuration(normalizeConfiguration(configuration, bindingId)));
return oldConfiguration != null ? Response.ok(oldConfiguration.getProperties()).build()
: Response.noContent().build();
} catch (IOException ex) {
logger.error("Cannot update configuration for service {}: {}", bindingId, ex.getMessage(), ex);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
private @Nullable Map<String, Object> normalizeConfiguration(@Nullable Map<String, Object> properties,
String bindingId) {
if (properties == null || properties.isEmpty()) {
return properties;
}
BindingInfo bindingInfo = this.bindingInfoRegistry.getBindingInfo(bindingId);
if (bindingInfo == null || bindingInfo.getConfigDescriptionURI() == null) {
return properties;
}
URI descURI = bindingInfo.getConfigDescriptionURI();
if (descURI != null) {
ConfigDescription configDesc = configDescRegistry.getConfigDescription(descURI);
if (configDesc != null) {
return ConfigUtil.normalizeTypes(properties, List.of(configDesc));
}
}
return properties;
}
private @Nullable String getConfigId(String bindingId) {
BindingInfo bindingInfo = this.bindingInfoRegistry.getBindingInfo(bindingId);
if (bindingInfo != null) {
return bindingInfo.getServiceId();
} else {
return null;
}
}
private BindingInfoDTO map(BindingInfo bindingInfo, Locale locale) {
URI configDescriptionURI = bindingInfo.getConfigDescriptionURI();
return new BindingInfoDTO(bindingInfo.getUID(), bindingInfo.getName(), bindingInfo.getAuthor(),
bindingInfo.getDescription(), configDescriptionURI != null ? configDescriptionURI.toString() : null);
}
}

View File

@ -88,17 +88,14 @@ public class FeatureInstaller implements ConfigurationListener {
public static final String EXTENSION_TYPE_TRANSFORMATION = "transformation";
public static final String EXTENSION_TYPE_UI = "ui";
public static final String EXTENSION_TYPE_VOICE = "voice";
public static final List<String> EXTENSION_TYPES = List.of(EXTENSION_TYPE_AUTOMATION, EXTENSION_TYPE_BINDING,
public static final Set<String> EXTENSION_TYPES = Set.of(EXTENSION_TYPE_AUTOMATION, EXTENSION_TYPE_BINDING,
EXTENSION_TYPE_MISC, EXTENSION_TYPE_PERSISTENCE, EXTENSION_TYPE_TRANSFORMATION, EXTENSION_TYPE_UI,
EXTENSION_TYPE_VOICE);
public static final String PREFIX = "openhab-";
public static final String PREFIX_PACKAGE = "package-";
public static final String SIMPLE_PACKAGE = "simple";
public static final String MINIMAL_PACKAGE = "minimal";
public static final String STANDARD_PACKAGE = "standard";
private static final String CFG_REMOTE = "remote";
private static final String PAX_URL_PID = "org.ops4j.pax.url.mvn";
private static final String ADDONS_PID = "org.openhab.addons";
@ -121,8 +118,6 @@ public class FeatureInstaller implements ConfigurationListener {
// configuration as this must be waited for before trying to add feature repos
private @Nullable Map<String, Object> configMapCache;
private @Nullable String currentPackage = null;
@Activate
public FeatureInstaller(final @Reference ConfigurationAdmin configurationAdmin,
final @Reference FeaturesService featuresService, final @Reference KarService karService,
@ -226,11 +221,6 @@ public class FeatureInstaller implements ConfigurationListener {
}
}
@Nullable
String getCurrentPackage() {
return currentPackage;
}
public void addAddon(String type, String id) {
try {
changeAddonConfig(type, id, Collection::add);
@ -267,12 +257,13 @@ public class FeatureInstaller implements ConfigurationListener {
Dictionary<String, Object> felixProperties = configurations[0].getProperties();
String addonsDirectory = (String) felixProperties.get("felix.fileinstall.dir");
if (addonsDirectory != null) {
return Files.list(Path.of(addonsDirectory)).map(Path::getFileName).map(Path::toString)
.filter(file -> file.endsWith(".kar"))
try (Stream<Path> files = Files.list(Path.of(addonsDirectory))) {
return files.map(Path::getFileName).map(Path::toString).filter(file -> file.endsWith(".kar"))
.map(karFileName -> karFileName.substring(0, karFileName.lastIndexOf(".")))
.allMatch(karRepos::contains);
}
}
}
} catch (Exception ignored) {
}
logger.warn("Could not determine addons folder, its content or the list of installed repositories!");
@ -399,11 +390,11 @@ public class FeatureInstaller implements ConfigurationListener {
for (String type : EXTENSION_TYPES) {
Object configValue = config.get(type);
if (configValue instanceof String) {
if (configValue instanceof String addonString) {
try {
Feature[] features = featuresService.listInstalledFeatures();
String typePrefix = PREFIX + type + "-";
Set<String> configFeatureNames = Arrays.stream(((String) configValue).split(",")) //
Set<String> configFeatureNames = Arrays.stream(addonString.split(",")) //
.map(String::strip) //
.filter(not(String::isEmpty)) //
.map(addon -> typePrefix + addon) //
@ -531,9 +522,8 @@ public class FeatureInstaller implements ConfigurationListener {
private boolean installPackage(final Map<String, Object> config) {
boolean configChanged = false;
Object packageName = config.get(OpenHAB.CFG_PACKAGE);
if (packageName instanceof String) {
currentPackage = (String) packageName;
String fullName = PREFIX + PREFIX_PACKAGE + ((String) packageName).strip();
if (packageName instanceof String currentPackage) {
String fullName = PREFIX + PREFIX_PACKAGE + currentPackage.strip();
if (!MINIMAL_PACKAGE.equals(currentPackage)) {
configChanged = installFeature(fullName);
}

View File

@ -13,20 +13,20 @@
package org.openhab.core.karaf.internal;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.Addon;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.addon.AddonService;
import org.openhab.core.addon.AddonType;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@ -40,34 +40,32 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Initial contribution
*/
@Component(name = "org.openhab.core.karafaddons")
@NonNullByDefault
public class KarafAddonService implements AddonService {
private static final String ADDONS_CONTENTTYPE = "application/vnd.openhab.feature;type=karaf";
private static final String ADDONS_CONTENT_TYPE = "application/vnd.openhab.feature;type=karaf";
private static final String ADDONS_AUTHOR = "openHAB";
private static final List<AddonType> ADDON_TYPES = List.of( //
new AddonType(FeatureInstaller.EXTENSION_TYPE_AUTOMATION, "Automation"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_BINDING, "Bindings"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_MISC, "Misc"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_VOICE, "Voice"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_PERSISTENCE, "Persistence"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_TRANSFORMATION, "Transformations"), //
new AddonType(FeatureInstaller.EXTENSION_TYPE_UI, "User Interfaces"));
private final Logger logger = LoggerFactory.getLogger(KarafAddonService.class);
private final List<AddonType> typeList = new ArrayList<>(FeatureInstaller.EXTENSION_TYPES.size());
private final FeaturesService featuresService;
private final FeatureInstaller featureInstaller;
private final BindingInfoRegistry bindingInfoRegistry;
private final AddonInfoRegistry addonInfoRegistry;
@Activate
public KarafAddonService(final @Reference FeatureInstaller featureInstaller,
final @Reference FeaturesService featuresService,
final @Reference BindingInfoRegistry bindingInfoRegistry) {
final @Reference FeaturesService featuresService, @Reference AddonInfoRegistry addonInfoRegistry) {
this.featureInstaller = featureInstaller;
this.featuresService = featuresService;
this.bindingInfoRegistry = bindingInfoRegistry;
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_AUTOMATION, "Automation"));
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_BINDING, "Bindings"));
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_MISC, "Misc"));
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_VOICE, "Voice"));
if (!FeatureInstaller.SIMPLE_PACKAGE.equals(featureInstaller.getCurrentPackage())) {
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_PERSISTENCE, "Persistence"));
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_TRANSFORMATION, "Transformations"));
typeList.add(new AddonType(FeatureInstaller.EXTENSION_TYPE_UI, "User Interfaces"));
}
this.addonInfoRegistry = addonInfoRegistry;
}
@Override
@ -85,10 +83,10 @@ public class KarafAddonService implements AddonService {
}
@Override
public List<Addon> getAddons(Locale locale) {
public List<Addon> getAddons(@Nullable Locale locale) {
try {
return Arrays.stream(featuresService.listFeatures()).filter(this::isAddon).map(this::getAddon)
.sorted(Comparator.comparing(Addon::getLabel)).collect(Collectors.toList());
.sorted(Comparator.comparing(Addon::getLabel)).toList();
} catch (Exception e) {
logger.error("Exception while retrieving features: {}", e.getMessage());
return List.of();
@ -101,7 +99,7 @@ public class KarafAddonService implements AddonService {
}
@Override
public Addon getAddon(String id, Locale locale) {
public @Nullable Addon getAddon(String id, @Nullable Locale locale) {
Feature feature;
try {
feature = featuresService.getFeature(FeatureInstaller.PREFIX + id);
@ -112,62 +110,46 @@ public class KarafAddonService implements AddonService {
}
}
private @Nullable String getDefaultDocumentationLink(String type, String name) {
return switch (type) {
case FeatureInstaller.EXTENSION_TYPE_AUTOMATION -> "https://www.openhab.org/addons/automation/" + name
+ "/";
case FeatureInstaller.EXTENSION_TYPE_BINDING -> "https://www.openhab.org/addons/bindings/" + name + "/";
case FeatureInstaller.EXTENSION_TYPE_PERSISTENCE -> "https://www.openhab.org/addons/persistence/" + name
+ "/";
case FeatureInstaller.EXTENSION_TYPE_TRANSFORMATION -> "https://www.openhab.org/addons/transformations/"
+ name + "/";
case FeatureInstaller.EXTENSION_TYPE_VOICE -> "https://www.openhab.org/addons/voice/" + name + "/";
default -> null;
};
}
private Addon getAddon(Feature feature) {
String name = getName(feature.getName());
String type = getType(feature.getName());
String link = null;
String configDescriptionURI = "";
switch (type) {
case FeatureInstaller.EXTENSION_TYPE_AUTOMATION:
link = "https://www.openhab.org/addons/automation/" + name + "/";
break;
case FeatureInstaller.EXTENSION_TYPE_BINDING:
link = "https://www.openhab.org/addons/bindings/" + name + "/";
BindingInfo bindingInfo = bindingInfoRegistry.getBindingInfo(name);
if (bindingInfo != null) {
URI uri = bindingInfo.getConfigDescriptionURI();
if (uri != null) {
configDescriptionURI = uri.toString();
}
}
break;
case FeatureInstaller.EXTENSION_TYPE_MISC:
// Not possible to define URL
break;
case FeatureInstaller.EXTENSION_TYPE_PERSISTENCE:
link = "https://www.openhab.org/addons/persistence/" + name + "/";
break;
case FeatureInstaller.EXTENSION_TYPE_TRANSFORMATION:
link = "https://www.openhab.org/addons/transformations/" + name + "/";
break;
case FeatureInstaller.EXTENSION_TYPE_UI:
// Not possible to define URL
break;
case FeatureInstaller.EXTENSION_TYPE_VOICE:
link = "https://www.openhab.org/addons/voice/" + name + "/";
break;
default:
break;
String id = type + Addon.ADDON_SEPARATOR + name;
boolean isInstalled = featuresService.isInstalled(feature);
Addon.Builder addon = Addon.create(id).withContentType(ADDONS_CONTENT_TYPE).withType(type)
.withVersion(feature.getVersion()).withAuthor(ADDONS_AUTHOR, true).withInstalled(isInstalled);
AddonInfo addonInfo = addonInfoRegistry.getAddonInfo(id);
if (isInstalled && addonInfo != null) {
// only enrich if this add-on is installed, otherwise wrong data might be added
addon = addon.withLabel(addonInfo.getName()).withDescription(addonInfo.getDescription())
.withCountries(addonInfo.getCountries()).withLink(getDefaultDocumentationLink(type, name))
.withConfigDescriptionURI(addonInfo.getConfigDescriptionURI());
} else {
addon = addon.withLabel(feature.getDescription()).withLink(getDefaultDocumentationLink(type, name));
}
// for openHAB add-on bundles the package is the same as the bundle name
List<String> packages = feature.getBundles().stream().filter(bundle -> !bundle.isDependency()).map(bundle -> {
String location = bundle.getLocation();
location = location.substring(0, location.lastIndexOf("/")); // strip version
location = location.substring(location.lastIndexOf("/") + 1); // strip groupId and protocol
return location;
}).collect(Collectors.toList());
return Addon.create(type + "-" + name).withType(type).withLabel(feature.getDescription())
.withVersion(feature.getVersion()).withContentType(ADDONS_CONTENTTYPE).withLink(link)
.withLoggerPackages(packages).withAuthor(ADDONS_AUTHOR, true)
.withConfigDescriptionURI(configDescriptionURI).withInstalled(featuresService.isInstalled(feature))
.build();
return addon.build();
}
@Override
public List<AddonType> getTypes(Locale locale) {
return typeList;
public List<AddonType> getTypes(@Nullable Locale locale) {
return ADDON_TYPES;
}
@Override
@ -181,29 +163,19 @@ public class KarafAddonService implements AddonService {
}
@Override
public String getAddonId(URI addonURI) {
public @Nullable String getAddonId(URI addonURI) {
return null;
}
private String substringAfter(String str, String separator) {
int index = str.indexOf(separator);
return index == -1 ? "" : str.substring(index + separator.length());
}
private String substringBefore(String str, String separator) {
int index = str.indexOf(separator);
private String getType(String name) {
String str = name.startsWith(FeatureInstaller.PREFIX) ? name.substring(FeatureInstaller.PREFIX.length()) : name;
int index = str.indexOf(Addon.ADDON_SEPARATOR);
return index == -1 ? str : str.substring(0, index);
}
private String getType(String name) {
return substringBefore(
name.startsWith(FeatureInstaller.PREFIX) ? name.substring(FeatureInstaller.PREFIX.length()) : name,
"-");
}
private String getName(String name) {
return substringAfter(
name.startsWith(FeatureInstaller.PREFIX) ? name.substring(FeatureInstaller.PREFIX.length()) : name,
"-");
String str = name.startsWith(FeatureInstaller.PREFIX) ? name.substring(FeatureInstaller.PREFIX.length()) : name;
int index = str.indexOf(Addon.ADDON_SEPARATOR);
return index == -1 ? "" : str.substring(index + Addon.ADDON_SEPARATOR.length());
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="magic" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Magic Binding</name>
<description>This is the Magic binding for openHAB.</description>
</addon:addon>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="magic" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Magic Binding</name>
<description>This is the Magic binding for openHAB.</description>
</binding:binding>

View File

@ -27,12 +27,13 @@ import org.eclipse.jdt.annotation.Nullable;
*/
public class Addon {
public static final Set<String> CODE_MATURITY_LEVELS = Set.of("alpha", "beta", "mature", "stable");
public static final String ADDON_SEPARATOR = "-";
private final String id;
private final String label;
private final String version;
private final @Nullable String maturity;
private boolean compatible;
private final boolean compatible;
private final String contentType;
private final @Nullable String link;
private final String author;
@ -43,7 +44,7 @@ public class Addon {
private final @Nullable String detailedDescription;
private final String configDescriptionURI;
private final String keywords;
private final String countries;
private final List<String> countries;
private final @Nullable String license;
private final String connection;
private final @Nullable String backgroundColor;
@ -69,7 +70,7 @@ public class Addon {
* @param detailedDescription the detailed description of the add-on (may be null)
* @param configDescriptionURI the URI to the configuration description for this add-on
* @param keywords the keywords for this add-on
* @param countries a comma-separated list of ISO 3166 codes relevant to this add-on
* @param countries a list of ISO 3166 codes relevant to this add-on
* @param license the SPDX license identifier
* @param connection a string describing the type of connection (local or cloud, push or pull...) this add-on uses,
* if applicable.
@ -81,7 +82,7 @@ public class Addon {
private Addon(String id, String type, String label, String version, @Nullable String maturity, boolean compatible,
String contentType, @Nullable String link, String author, boolean verifiedAuthor, boolean installed,
@Nullable String description, @Nullable String detailedDescription, String configDescriptionURI,
String keywords, String countries, @Nullable String license, String connection,
String keywords, List<String> countries, @Nullable String license, String connection,
@Nullable String backgroundColor, @Nullable String imageLink, @Nullable Map<String, Object> properties,
List<String> loggerPackages) {
this.id = id;
@ -122,6 +123,10 @@ public class Addon {
return id;
}
public String getUID() {
return type + ADDON_SEPARATOR + id;
}
/**
* The label of the add-on
*/
@ -207,9 +212,9 @@ public class Addon {
}
/**
* A comma-separated list of ISO 3166 codes relevant to this add-on
* A list of ISO 3166 codes relevant to this add-on
*/
public String getCountries() {
public List<String> getCountries() {
return countries;
}
@ -221,7 +226,7 @@ public class Addon {
}
/**
* A string describing the type of connection (local or cloud, push or pull...) this add-on uses, if applicable.
* A string describing the type of connection (local, cloud, cloudDiscovery) this add-on uses, if applicable.
*/
public String getConnection() {
return connection;
@ -289,7 +294,7 @@ public class Addon {
private @Nullable String detailedDescription;
private String configDescriptionURI = "";
private String keywords = "";
private String countries = "";
private List<String> countries = List.of();
private @Nullable String license;
private String connection = "";
private @Nullable String backgroundColor;
@ -372,7 +377,7 @@ public class Addon {
return this;
}
public Builder withCountries(String countries) {
public Builder withCountries(List<String> countries) {
this.countries = countries;
return this;
}

View File

@ -0,0 +1,58 @@
/**
* 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;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.internal.addon.AddonI18nUtil;
import org.osgi.framework.Bundle;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* This OSGi service could be used to localize the add-on info using the I18N mechanism of the openHAB
* framework.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(immediate = true, service = { AddonI18nLocalizationService.class })
@NonNullByDefault
public class AddonI18nLocalizationService {
private final AddonI18nUtil addonI18NUtil;
@Activate
public AddonI18nLocalizationService(final @Reference TranslationProvider i18nProvider) {
this.addonI18NUtil = new AddonI18nUtil(i18nProvider);
}
/**
* Localizes an add-on info.
*
* @param bundle the bundle the i18n resources are located
* @param addonInfo the add-on info that should be localized
* @param locale the locale it should be localized to
* @return a localized add-on info on success, a non-localized one on error (e.g. no translation is found).
*/
public AddonInfo createLocalizedAddonInfo(Bundle bundle, AddonInfo addonInfo, @Nullable Locale locale) {
String addonInfoUID = addonInfo.getId();
String name = addonI18NUtil.getName(bundle, addonInfoUID, addonInfo.getName(), locale);
String description = addonI18NUtil.getDescription(bundle, addonInfoUID, addonInfo.getDescription(), locale);
return AddonInfo.builder(addonInfo).withName(name).withDescription(description).build();
}
}

View File

@ -0,0 +1,234 @@
/**
* 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;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Identifiable;
/**
* The {@link AddonInfo} class contains general information about an add-on.
* <p>
* Any add-on information is provided by a {@link AddonInfoProvider} and can also be retrieved through the
* {@link AddonInfoRegistry}.
*
* @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 AddonInfo implements Identifiable<String> {
private static final Set<String> SUPPORTED_ADDON_TYPES = Set.of("automation", "binding", "misc", "persistence",
"transformation", "ui", "voice");
private final String id;
private final String type;
private final String name;
private final String description;
private final @Nullable String author;
private final @Nullable String connection;
private final List<String> countries;
private final @Nullable String configDescriptionURI;
private final String serviceId;
private AddonInfo(String id, String type, String name, String description, @Nullable String author,
@Nullable String connection, List<String> countries, @Nullable String configDescriptionURI,
@Nullable String serviceId) throws IllegalArgumentException {
// mandatory fields
if (id.isBlank()) {
throw new IllegalArgumentException("The ID must neither be null nor empty!");
}
if (!SUPPORTED_ADDON_TYPES.contains(type)) {
throw new IllegalArgumentException(
"The type must be one of [" + String.join(", ", SUPPORTED_ADDON_TYPES) + "]");
}
if (name.isBlank()) {
throw new IllegalArgumentException("The name must neither be null nor empty!");
}
if (description.isBlank()) {
throw new IllegalArgumentException("The description must neither be null nor empty!");
}
this.id = id;
this.type = type;
this.name = name;
this.description = description;
// optional fields
this.author = author;
this.connection = connection;
this.countries = countries;
this.configDescriptionURI = configDescriptionURI;
this.serviceId = Objects.requireNonNullElse(serviceId, type + "." + id);
}
/**
* Returns an unique identifier for the add-on (e.g. "binding-hue").
*
* @return an identifier for the add-on
*/
@Override
public String getUID() {
return type + Addon.ADDON_SEPARATOR + id;
}
/**
* Returns the id part of the UID
*
* @return the identifier
*/
public String getId() {
return id;
}
/**
* Returns a human-readable name for the add-on (e.g. "HUE Binding").
*
* @return a human-readable name for the add-on (neither null, nor empty)
*/
public String getName() {
return name;
}
public String getType() {
return type;
}
public String getServiceId() {
return serviceId;
}
/**
* Returns a human-readable description for the add-on
* (e.g. "Discovers and controls HUE bulbs").
*
* @return a human-readable description for the add-on
*/
public String getDescription() {
return description;
}
/**
* Returns the author of the add-on (e.g. "Max Mustermann").
*
* @return the author of the add-on (could be null or empty)
*/
public @Nullable String getAuthor() {
return author;
}
/**
* Returns the link to a concrete {@link org.openhab.core.config.core.ConfigDescription}.
*
* @return the link to a concrete ConfigDescription (could be <code>null</code>>)
*/
public @Nullable String getConfigDescriptionURI() {
return configDescriptionURI;
}
public List<String> getCountries() {
return countries;
}
public static Builder builder(String id, String type) {
return new Builder(id, type);
}
public static Builder builder(AddonInfo addonInfo) {
return new Builder(addonInfo);
}
public static class Builder {
private final String id;
private final String type;
private String name = "";
private String description = "";
private @Nullable String author;
private @Nullable String connection;
private List<String> countries = List.of();
private @Nullable String configDescriptionURI = "";
private @Nullable String serviceId;
private Builder(String id, String type) {
this.id = id;
this.type = type;
}
private Builder(AddonInfo addonInfo) {
this.id = addonInfo.id;
this.type = addonInfo.type;
this.name = addonInfo.name;
this.description = addonInfo.description;
this.author = addonInfo.author;
this.connection = addonInfo.connection;
this.countries = addonInfo.countries;
this.configDescriptionURI = addonInfo.configDescriptionURI;
this.serviceId = addonInfo.serviceId;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withDescription(String description) {
this.description = description;
return this;
}
public Builder withAuthor(@Nullable String author) {
this.author = author;
return this;
}
public Builder withConnection(@Nullable String connection) {
this.connection = connection;
return this;
}
public Builder withCountries(@Nullable String countries) {
this.countries = List.of(Objects.requireNonNull(countries, "").split(","));
return this;
}
public Builder withCountries(List<String> countries) {
this.countries = countries;
return this;
}
public Builder withConfigDescriptionURI(@Nullable String configDescriptionURI) {
this.configDescriptionURI = configDescriptionURI;
return this;
}
public Builder withServiceId(@Nullable String serviceId) {
this.serviceId = serviceId;
return this;
}
/**
* Build an {@link AddonInfo} from this builder
*
* @return the add-on info object
* @throws IllegalArgumentException if any of the information in this builder is invalid
*/
public AddonInfo build() throws IllegalArgumentException {
return new AddonInfo(id, type, name, description, author, connection, countries, configDescriptionURI,
serviceId);
}
}
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding;
package org.openhab.core.addon;
import java.util.Locale;
import java.util.Set;
@ -19,16 +19,16 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link BindingInfoProvider} is a service interface providing {@link BindingInfo} objects. All registered
* {@link BindingInfoProvider} services are tracked by the {@link BindingInfoRegistry} and provided as one common
* The {@link AddonInfoProvider} is a service interface providing {@link AddonInfo} objects. All registered
* {@link AddonInfoProvider} services are tracked by the {@link AddonInfoRegistry} and provided as one common
* collection.
*
* @author Michael Grammling - Initial contribution
*
* @see BindingInfoRegistry
* @see AddonInfoRegistry
*/
@NonNullByDefault
public interface BindingInfoProvider {
public interface AddonInfoProvider {
/**
* Returns the binding information for the specified binding ID and locale (language),
@ -39,7 +39,7 @@ public interface BindingInfoProvider {
* @return a localized binding information object (could be null)
*/
@Nullable
BindingInfo getBindingInfo(@Nullable String id, @Nullable Locale locale);
AddonInfo getAddonInfo(@Nullable String id, @Nullable Locale locale);
/**
* Returns all binding information in the specified locale (language) this provider contains.
@ -48,5 +48,5 @@ public interface BindingInfoProvider {
* @return a localized set of all binding information this provider contains
* (could be empty)
*/
Set<BindingInfo> getBindingInfos(@Nullable Locale locale);
Set<AddonInfo> getAddonInfos(@Nullable Locale locale);
}

View File

@ -0,0 +1,95 @@
/**
* 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;
import java.util.Collection;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
/**
* The {@link AddonInfoRegistry} provides access to {@link AddonInfo} objects.
* It tracks {@link AddonInfoProvider} <i>OSGi</i> services to collect all {@link AddonInfo} objects.
*
* @author Dennis Nobel - Initial contribution
* @author Michael Grammling - Initial contribution, added locale support
*/
@Component(immediate = true, service = AddonInfoRegistry.class)
@NonNullByDefault
public class AddonInfoRegistry {
private final Collection<AddonInfoProvider> addonInfoProviders = new CopyOnWriteArrayList<>();
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
addonInfoProviders.add(addonInfoProvider);
}
protected void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
addonInfoProviders.remove(addonInfoProvider);
}
/**
* Returns the add-on information for the specified add-on ID, or {@code null} if no add-on information could be
* found.
*
* @param id the ID to be looked
* @return a add-on information object (could be null)
*/
public @Nullable AddonInfo getAddonInfo(String id) {
return getAddonInfo(id, null);
}
/**
* Returns the add-on information for the specified add-on ID and locale (language),
* or {@code null} if no add-on information could be found.
*
* @param id the ID to be looked for
* @param locale the locale to be used for the add-on information (could be null)
* @return a localized add-on information object (could be null)
*/
public @Nullable AddonInfo getAddonInfo(String id, @Nullable Locale locale) {
return addonInfoProviders.stream().map(p -> p.getAddonInfo(id, locale)).filter(Objects::nonNull).findAny()
.orElse(null);
}
/**
* Returns all add-on information this registry contains.
*
* @return a set of all add-on information this registry contains (not null, could be empty)
*/
public Set<AddonInfo> getAddonInfos() {
return getAddonInfos(null);
}
/**
* Returns all add-on information in the specified locale (language) this registry contains.
*
* @param locale the locale to be used for the add-on information (could be null)
* @return a localized set of all add-on information this registry contains
* (not null, could be empty)
*/
public Set<AddonInfo> getAddonInfos(@Nullable Locale locale) {
return addonInfoProviders.stream().map(provider -> provider.getAddonInfos(locale)).flatMap(Set::stream)
.collect(Collectors.toUnmodifiableSet());
}
}

View File

@ -55,7 +55,7 @@ public class AddonType {
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + id.hashCode();
return result;
}
@ -70,14 +70,6 @@ public class AddonType {
if (getClass() != obj.getClass()) {
return false;
}
AddonType other = (AddonType) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
return id.equals(((AddonType) obj).id);
}
}

View File

@ -1,137 +0,0 @@
/**
* 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.binding;
import java.net.URI;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.registry.Identifiable;
/**
* The {@link BindingInfo} class contains general information about a binding.
* <p>
* Any binding information are provided by a {@link BindingInfoProvider} and can also be retrieved through the
* {@link BindingInfoRegistry}.
* <p>
* <b>Hint:</b> This class is immutable.
*
* @author Michael Grammling - Initial contribution
* @author Andre Fuechsel - Made author tag optional
*/
@NonNullByDefault
public class BindingInfo implements Identifiable<String> {
/**
* The default service ID prefix.
*/
public static final String DEFAULT_SERVICE_ID_PREFIX = "binding.";
private String id;
private String name;
private @Nullable String description;
private @Nullable String author;
private @Nullable URI configDescriptionURI;
private String serviceId;
/**
* Creates a new instance of this class with the specified parameters.
*
* @param id the identifier for the binding (must neither be null, nor empty)
* @param name a human readable name for the binding (must neither be null, nor empty)
* @param description a human readable description for the binding (could be null or empty)
* @param author the author of the binding (could be null or empty)
* @param serviceId the service id of the main service of the binding (can be null)
* @param configDescriptionURI the link to a concrete ConfigDescription (could be null)
* @throws IllegalArgumentException if the identifier or the name are null or empty
*/
public BindingInfo(String id, String name, @Nullable String description, @Nullable String author,
@Nullable String serviceId, @Nullable URI configDescriptionURI) throws IllegalArgumentException {
if ((id == null) || (id.isEmpty())) {
throw new IllegalArgumentException("The ID must neither be null nor empty!");
}
if ((name == null) || (name.isEmpty())) {
throw new IllegalArgumentException("The name must neither be null nor empty!");
}
this.id = id;
this.name = name;
this.description = description;
this.author = author;
this.serviceId = serviceId != null ? serviceId : DEFAULT_SERVICE_ID_PREFIX + id;
this.configDescriptionURI = configDescriptionURI;
}
/**
* Returns an identifier for the binding (e.g. "hue").
*
* @return an identifier for the binding (neither null, nor empty)
*/
@Override
public String getUID() {
return id;
}
/**
* Returns a human readable name for the binding (e.g. "HUE Binding").
*
* @return a human readable name for the binding (neither null, nor empty)
*/
public String getName() {
return name;
}
/**
* Returns a human readable description for the binding
* (e.g. "Discovers and controls HUE bulbs").
*
* @return a human readable description for the binding (could be null or empty)
*/
public @Nullable String getDescription() {
return description;
}
/**
* Returns the author of the binding (e.g. "Max Mustermann").
*
* @return the author of the binding (could be null or empty)
*/
public @Nullable String getAuthor() {
return author;
}
/**
* Returns the service ID of the bindings main service, that can be configured.
*
* @return service ID
*/
public String getServiceId() {
return serviceId;
}
/**
* Returns the link to a concrete {@link ConfigDescription}.
*
* @return the link to a concrete ConfigDescription (could be null)
*/
public @Nullable URI getConfigDescriptionURI() {
return configDescriptionURI;
}
@Override
public String toString() {
return "BindingInfo [id=" + id + ", name=" + name + ", description=" + description + ", author=" + author
+ ", configDescriptionURI=" + configDescriptionURI + "]";
}
}

View File

@ -1,108 +0,0 @@
/**
* 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.binding;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
/**
* The {@link BindingInfoRegistry} provides access to {@link BindingInfo} objects.
* It tracks {@link BindingInfoProvider} <i>OSGi</i> services to collect all {@link BindingInfo} objects.
*
* @author Dennis Nobel - Initial contribution
* @author Michael Grammling - Initial contribution, added locale support
*/
@Component(immediate = true, service = BindingInfoRegistry.class)
@NonNullByDefault
public class BindingInfoRegistry {
private final Collection<BindingInfoProvider> bindingInfoProviders = new CopyOnWriteArrayList<>();
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void addBindingInfoProvider(BindingInfoProvider bindingInfoProvider) {
if (bindingInfoProvider != null) {
bindingInfoProviders.add(bindingInfoProvider);
}
}
protected void removeBindingInfoProvider(BindingInfoProvider bindingInfoProvider) {
if (bindingInfoProvider != null) {
bindingInfoProviders.remove(bindingInfoProvider);
}
}
/**
* Returns the binding information for the specified binding ID, or {@code null} if no binding information could be
* found.
*
* @param id the ID to be looked for (could be null or empty)
* @return a binding information object (could be null)
*/
public @Nullable BindingInfo getBindingInfo(@Nullable String id) {
return getBindingInfo(id, null);
}
/**
* Returns the binding information for the specified binding ID and locale (language),
* or {@code null} if no binding information could be found.
*
* @param id the ID to be looked for (could be null or empty)
* @param locale the locale to be used for the binding information (could be null)
* @return a localized binding information object (could be null)
*/
public @Nullable BindingInfo getBindingInfo(@Nullable String id, @Nullable Locale locale) {
for (BindingInfoProvider bindingInfoProvider : bindingInfoProviders) {
BindingInfo bindingInfo = bindingInfoProvider.getBindingInfo(id, locale);
if (bindingInfo != null) {
return bindingInfo;
}
}
return null;
}
/**
* Returns all binding information this registry contains.
*
* @return a set of all binding information this registry contains (not null, could be empty)
*/
public Set<BindingInfo> getBindingInfos() {
return getBindingInfos(null);
}
/**
* Returns all binding information in the specified locale (language) this registry contains.
*
* @param locale the locale to be used for the binding information (could be null)
* @return a localized set of all binding information this registry contains
* (not null, could be empty)
*/
public Set<BindingInfo> getBindingInfos(@Nullable Locale locale) {
Set<BindingInfo> allBindingInfos = new LinkedHashSet<>(bindingInfoProviders.size());
for (BindingInfoProvider bindingInfoProvider : bindingInfoProviders) {
Set<BindingInfo> bindingInfos = bindingInfoProvider.getBindingInfos(locale);
allBindingInfos.addAll(bindingInfos);
}
return Collections.unmodifiableSet(allBindingInfos);
}
}

View File

@ -1,38 +0,0 @@
/**
* 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.binding.dto;
/**
* This is a data transfer object that is used to serialize binding info objects.
*
* @author Dennis Nobel - Initial contribution
*/
public class BindingInfoDTO {
public String author;
public String description;
public String id;
public String name;
public String configDescriptionURI;
public BindingInfoDTO() {
}
public BindingInfoDTO(String id, String name, String author, String description, String configDescriptionURI) {
this.id = id;
this.name = name;
this.author = author;
this.description = description;
this.configDescriptionURI = configDescriptionURI;
}
}

View File

@ -1,62 +0,0 @@
/**
* 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.binding.i18n;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.internal.i18n.BindingI18nUtil;
import org.openhab.core.i18n.TranslationProvider;
import org.osgi.framework.Bundle;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* This OSGi service could be used to localize the binding info using the I18N mechanism of the openHAB
* framework.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(immediate = true, service = { BindingI18nLocalizationService.class })
@NonNullByDefault
public class BindingI18nLocalizationService {
private final BindingI18nUtil bindingI18nUtil;
@Activate
public BindingI18nLocalizationService(final @Reference TranslationProvider i18nProvider) {
this.bindingI18nUtil = new BindingI18nUtil(i18nProvider);
}
/**
* Localizes a binding info.
*
* @param bundle the bundle the i18n resources are located
* @param bindingInfo the binding info that should be localized
* @param locale the locale it should be localized to
* @return a localized binding info on success, a non-localized one on error (e.g. no translation is found).
*/
public BindingInfo createLocalizedBindingInfo(Bundle bundle, BindingInfo bindingInfo, @Nullable Locale locale) {
String bindingInfoUID = bindingInfo.getUID();
String name = bindingI18nUtil.getName(bundle, bindingInfoUID, bindingInfo.getName(), locale);
String description = bindingI18nUtil.getDescription(bundle, bindingInfoUID, bindingInfo.getDescription(),
locale);
return new BindingInfo(bindingInfoUID, name == null ? bindingInfo.getName() : name,
description == null ? bindingInfo.getDescription() : description, bindingInfo.getAuthor(),
bindingInfo.getServiceId(), bindingInfo.getConfigDescriptionURI());
}
}

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.internal.i18n;
package org.openhab.core.internal.addon;
import java.util.Locale;
@ -21,18 +21,18 @@ import org.openhab.core.i18n.TranslationProvider;
import org.osgi.framework.Bundle;
/**
* The {@link BindingI18nUtil} uses the {@link TranslationProvider} to resolve the
* The {@link AddonI18nUtil} uses the {@link TranslationProvider} to resolve the
* localized texts. It automatically infers the key if the default text is not a
* constant.
*
* @author Dennis Nobel - Initial contribution
*/
@NonNullByDefault
public class BindingI18nUtil {
public class AddonI18nUtil {
private final TranslationProvider i18nProvider;
public BindingI18nUtil(TranslationProvider i18nProvider) {
public AddonI18nUtil(TranslationProvider i18nProvider) {
this.i18nProvider = i18nProvider;
}

View File

@ -19,6 +19,7 @@
<modules>
<module>org.openhab.core.addon.marketplace</module>
<module>org.openhab.core.addon.marketplace.karaf</module>
<module>org.openhab.core.addon.xml</module>
<module>org.openhab.core.auth.jaas</module>
<module>org.openhab.core.auth.oauth2client</module>
<module>org.openhab.core.automation</module>
@ -38,7 +39,6 @@
<module>org.openhab.core.config.xml</module>
<module>org.openhab.core</module>
<module>org.openhab.core.audio</module>
<module>org.openhab.core.binding.xml</module>
<module>org.openhab.core.ephemeris</module>
<module>org.openhab.core.addon.sample</module>
<module>org.openhab.core.id</module>

View File

@ -47,7 +47,7 @@
<bundle start-level="75">mvn:org.openhab.core.bundles/org.openhab.core.config.xml/${project.version}</bundle>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core/${project.version}</bundle>
<feature dependency="true">openhab-core-storage-json</feature>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.binding.xml/${project.version}</bundle>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.addon.xml/${project.version}</bundle>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.ephemeris/${project.version}</bundle>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.id/${project.version}</bundle>
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.persistence/${project.version}</bundle>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.core.binding.xml.tests</name>
<name>org.openhab.core.addon.xml.tests</name>
<comment></comment>
<projects>
</projects>

View File

@ -1,13 +1,13 @@
-include: ../itest-common.bndrun
Bundle-SymbolicName: ${project.artifactId}
Fragment-Host: org.openhab.core.binding.xml
Fragment-Host: org.openhab.core.addon.xml
-runblacklist: \
bnd.identity;id='org.osgi.service.cm'
-runrequires: \
bnd.identity;id='org.openhab.core.binding.xml.tests'
bnd.identity;id='org.openhab.core.addon.xml.tests'
#
# done
@ -52,10 +52,10 @@ Fragment-Host: org.openhab.core.binding.xml
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.binding.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.binding.xml.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.addon.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.addon.xml.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.test;version='[4.0.0,4.0.1)'

View File

@ -9,8 +9,8 @@
<version>4.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.core.binding.xml.tests</artifactId>
<artifactId>org.openhab.core.addon.xml.tests</artifactId>
<name>openHAB Core :: Integration Tests :: Binding XML Tests</name>
<name>openHAB Core :: Integration Tests :: Add-on XML Tests</name>
</project>

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.xml.test;
package org.openhab.core.addon.xml.test;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@ -23,8 +23,8 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoRegistry;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.internal.i18n.I18nProviderImpl;
import org.openhab.core.test.java.JavaOSGiTest;
@ -35,38 +35,38 @@ import org.osgi.service.cm.ConfigurationAdmin;
* @author Dennis Nobel - Initial contribution
*/
@NonNullByDefault
public class BindingInfoI18nTest extends JavaOSGiTest {
public class AddonInfoI18nTest extends JavaOSGiTest {
private static final String TEST_BUNDLE_NAME = "acmeweather.bundle";
private @NonNullByDefault({}) BindingInfoRegistry bindingInfoRegistry;
private @NonNullByDefault({}) BindingInstaller bindingInstaller;
private @NonNullByDefault({}) AddonInfoRegistry addonInfoRegistry;
private @NonNullByDefault({}) AddonInstaller addonInstaller;
@BeforeEach
public void setUp() {
bindingInfoRegistry = getService(BindingInfoRegistry.class);
assertThat(bindingInfoRegistry, is(notNullValue()));
bindingInstaller = new BindingInstaller(this::waitForAssert, bindingInfoRegistry, bundleContext);
addonInfoRegistry = getService(AddonInfoRegistry.class);
assertThat(addonInfoRegistry, is(notNullValue()));
addonInstaller = new AddonInstaller(this::waitForAssert, addonInfoRegistry, bundleContext);
}
@Test
public void assertBindingInfosWereLocalizedInGerman() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos(Locale.GERMAN);
BindingInfo bindingInfo = bindingInfos.iterator().next();
public void assertAddonInfosWereLocalizedInGerman() throws Exception {
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<AddonInfo> addonInfos = addonInfoRegistry.getAddonInfos(Locale.GERMAN);
AddonInfo addonInfo = addonInfos.iterator().next();
assertThat(bindingInfo, is(notNullValue()));
assertThat(bindingInfo.getName(), is("ACME Wetter Binding"));
assertThat(bindingInfo.getDescription(), is(
assertThat(addonInfo, is(notNullValue()));
assertThat(addonInfo.getName(), is("ACME Wetter Binding"));
assertThat(addonInfo.getDescription(), is(
"Das ACME Wetter Binding stellt verschiedene Wetterdaten wie die Temperatur, die Luftfeuchtigkeit und den Luftdruck für konfigurierbare Orte vom ACME Wetterdienst bereit"));
});
}
@Test
public void assertBindingInfosWereLocalizedInDutch() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos(new Locale("nl"));
BindingInfo bindingInfo = bindingInfos.iterator().next();
public void assertAddonInfosWereLocalizedInDutch() throws Exception {
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<AddonInfo> bindingInfos = addonInfoRegistry.getAddonInfos(new Locale("nl"));
AddonInfo bindingInfo = bindingInfos.iterator().next();
assertThat(bindingInfo, is(notNullValue()));
assertThat(bindingInfo.getName(), is("ACME Weer Binding"));
@ -76,10 +76,10 @@ public class BindingInfoI18nTest extends JavaOSGiTest {
}
@Test
public void assertUsingOriginalBindingInfosIfProvidedLocaleIsNotSupported() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos(Locale.FRENCH);
BindingInfo bindingInfo = bindingInfos.iterator().next();
public void assertUsingOriginalAddonInfosIfProvidedLocaleIsNotSupported() throws Exception {
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<AddonInfo> bindingInfos = addonInfoRegistry.getAddonInfos(Locale.FRENCH);
AddonInfo bindingInfo = bindingInfos.iterator().next();
assertThat(bindingInfo, is(notNullValue()));
assertThat(bindingInfo.getName(), is("ACME Weather Binding"));
@ -113,10 +113,10 @@ public class BindingInfoI18nTest extends JavaOSGiTest {
waitForAssert(() -> assertThat(localeProvider.getLocale().toString(), is("de_DE")));
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
// use default locale
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos(null);
BindingInfo bindingInfo = bindingInfos.iterator().next();
Set<AddonInfo> bindingInfos = addonInfoRegistry.getAddonInfos(null);
AddonInfo bindingInfo = bindingInfos.iterator().next();
assertThat(bindingInfo, is(notNullValue()));
assertThat(bindingInfo.getName(), is("ACME Wetter Binding"));
assertThat(bindingInfo.getDescription(), is(

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.xml.test;
package org.openhab.core.addon.xml.test;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@ -24,8 +24,8 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.BindingInfoRegistry;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.ConfigDescriptionRegistry;
@ -36,60 +36,63 @@ import org.openhab.core.test.java.JavaOSGiTest;
* @author Wouter Born - Migrate tests from Groovy to Java
*/
@NonNullByDefault
public class BindingInfoTest extends JavaOSGiTest {
public class AddonInfoTest extends JavaOSGiTest {
private static final String TEST_BUNDLE_NAME = "BundleInfoTest.bundle";
private static final String TEST_BUNDLE_NAME2 = "BundleInfoTestNoAuthor.bundle";
private @NonNullByDefault({}) BindingInfoRegistry bindingInfoRegistry;
private @NonNullByDefault({}) AddonInfoRegistry addonInfoRegistry;
private @NonNullByDefault({}) ConfigDescriptionRegistry configDescriptionRegistry;
private @NonNullByDefault({}) BindingInstaller bindingInstaller;
private @NonNullByDefault({}) AddonInstaller addonInstaller;
@BeforeEach
public void setUp() {
bindingInfoRegistry = getService(BindingInfoRegistry.class);
assertThat(bindingInfoRegistry, is(notNullValue()));
addonInfoRegistry = getService(AddonInfoRegistry.class);
assertThat(addonInfoRegistry, is(notNullValue()));
configDescriptionRegistry = getService(ConfigDescriptionRegistry.class);
assertThat(configDescriptionRegistry, is(notNullValue()));
bindingInstaller = new BindingInstaller(this::waitForAssert, bindingInfoRegistry, bundleContext);
addonInstaller = new AddonInstaller(this::waitForAssert, addonInfoRegistry, bundleContext);
}
@Test
public void assertThatBindingInfoIsReadProperly() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos();
BindingInfo bindingInfo = bindingInfos.iterator().next();
assertThat(bindingInfo.getUID(), is("hue"));
assertThat(bindingInfo.getConfigDescriptionURI(), is(URI.create("binding:hue")));
assertThat(bindingInfo.getDescription(),
public void assertThatAddonInfoIsReadProperly() throws Exception {
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<AddonInfo> addonInfos = addonInfoRegistry.getAddonInfos();
AddonInfo addonInfo = addonInfos.iterator().next();
assertThat(addonInfo.getId(), is("hue"));
assertThat(addonInfo.getUID(), is("binding-hue"));
assertThat(addonInfo.getConfigDescriptionURI(), is("binding:hue"));
assertThat(addonInfo.getDescription(),
is("The hue Binding integrates the Philips hue system. It allows to control hue lights."));
assertThat(bindingInfo.getName(), is("hue Binding"));
assertThat(bindingInfo.getAuthor(), is("Deutsche Telekom AG"));
assertThat(addonInfo.getName(), is("hue Binding"));
assertThat(addonInfo.getAuthor(), is("Deutsche Telekom AG"));
});
}
@Test
public void assertThatBindingInfoWithoutAuthorIsReadProperly() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME2, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos();
BindingInfo bindingInfo = bindingInfos.iterator().next();
assertThat(bindingInfo.getUID(), is("hue"));
assertThat(bindingInfo.getConfigDescriptionURI(), is(URI.create("binding:hue")));
assertThat(bindingInfo.getDescription(),
public void assertThatAddonInfoWithoutAuthorIsReadProperly() throws Exception {
addonInstaller.exec(TEST_BUNDLE_NAME2, () -> {
Set<AddonInfo> addonInfos = addonInfoRegistry.getAddonInfos();
AddonInfo addonInfo = addonInfos.iterator().next();
assertThat(addonInfo.getId(), is("hue"));
assertThat(addonInfo.getUID(), is("binding-hue"));
assertThat(addonInfo.getConfigDescriptionURI(), is("foo:bar"));
assertThat(addonInfo.getDescription(),
is("The hue Binding integrates the Philips hue system. It allows to control hue lights."));
assertThat(bindingInfo.getName(), is("hue Binding"));
assertThat(bindingInfo.getAuthor(), is((String) null));
assertThat(addonInfo.getName(), is("hue Binding"));
assertThat(addonInfo.getAuthor(), is((String) null));
});
}
@Test
public void assertThatConfigWithOptionsAndFilterAreProperlyRead() throws Exception {
bindingInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<BindingInfo> bindingInfos = bindingInfoRegistry.getBindingInfos();
BindingInfo bindingInfo = bindingInfos.iterator().next();
addonInstaller.exec(TEST_BUNDLE_NAME, () -> {
Set<AddonInfo> bindingInfos = addonInfoRegistry.getAddonInfos();
AddonInfo bindingInfo = bindingInfos.iterator().next();
URI configDescriptionURI = Objects.requireNonNull(bindingInfo.getConfigDescriptionURI());
ConfigDescription configDescription = configDescriptionRegistry.getConfigDescription(configDescriptionURI);
String configDescriptionURI = Objects.requireNonNull(bindingInfo.getConfigDescriptionURI());
ConfigDescription configDescription = configDescriptionRegistry
.getConfigDescription(URI.create(configDescriptionURI));
List<ConfigDescriptionParameter> parameters = configDescription.getParameters();
assertThat(parameters.size(), is(2));

View File

@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.binding.xml.test;
package org.openhab.core.addon.xml.test;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@ -18,7 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.binding.BindingInfoRegistry;
import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.test.BundleCloseable;
import org.openhab.core.test.SyntheticBundleInstaller;
import org.osgi.framework.BundleContext;
@ -27,36 +27,35 @@ import org.osgi.framework.BundleContext;
* @author Markus Rathgeb - Initial contribution
*/
@NonNullByDefault
public class BindingInstaller {
public class AddonInstaller {
private final Consumer<Runnable> waitForAssert;
private final BindingInfoRegistry bindingInfoRegistry;
private final AddonInfoRegistry addonInfoRegistry;
private final BundleContext bc;
public BindingInstaller(Consumer<Runnable> waitForAssert, BindingInfoRegistry bindingInfoRegistry,
BundleContext bc) {
public AddonInstaller(Consumer<Runnable> waitForAssert, AddonInfoRegistry addonInfoRegistry, BundleContext bc) {
this.waitForAssert = waitForAssert;
this.bindingInfoRegistry = bindingInfoRegistry;
this.addonInfoRegistry = addonInfoRegistry;
this.bc = bc;
}
public void exec(final String bundleName, final Runnable func) throws Exception {
// Save the number of currently installed bundles.
final int initialNumberOfBindingInfos = bindingInfoRegistry.getBindingInfos().size();
final int initialNumberOfBindingInfos = addonInfoRegistry.getAddonInfos().size();
// install test bundle
try (BundleCloseable bundle = new BundleCloseable(SyntheticBundleInstaller.install(bc, bundleName))) {
assertThat(bundle, is(notNullValue()));
// Wait for correctly installed bundle.
waitForAssert.accept(() -> assertThat(bindingInfoRegistry.getBindingInfos().size(),
is(initialNumberOfBindingInfos + 1)));
waitForAssert.accept(
() -> assertThat(addonInfoRegistry.getAddonInfos().size(), is(initialNumberOfBindingInfos + 1)));
func.run();
}
// Wait for correctly uninstalled bundle.
waitForAssert.accept(
() -> assertThat(bindingInfoRegistry.getBindingInfos().size(), is(initialNumberOfBindingInfos)));
waitForAssert
.accept(() -> assertThat(addonInfoRegistry.getAddonInfos().size(), is(initialNumberOfBindingInfos)));
}
}

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd" id="hue">
<addon:addon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd" id="hue">
<type>binding</type>
<name>hue Binding</name>
<description>The hue Binding integrates the Philips hue system. It
allows to control hue lights.</description>
@ -29,4 +30,4 @@
</config-description>
</binding:binding>
</addon:addon>

View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd" id="hue">
<addon:addon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd" id="hue">
<type>binding</type>
<name>hue Binding</name>
<description>The hue Binding integrates the Philips hue system. It
allows to control hue lights.</description>
<!-- Dummy config -->
<config-description>
<config-description uri="foo:bar">
<parameter name="list" type="text" multiple="true" min="2" max="3">
<options>
@ -29,4 +30,4 @@
</config-description>
</binding:binding>
</addon:addon>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="acmeweather" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>ACME Weather Binding</name>
<description>The ACME Weather Binding requests the ACME Weather Service to show the current temperature, humidity and
pressure.</description>
</addon:addon>

View File

@ -56,10 +56,10 @@ Fragment-Host: org.openhab.core.auth.oauth2client
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.auth.oauth2client;version='[4.0.0,4.0.1)',\
org.openhab.core.auth.oauth2client.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.io.net;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.test;version='[4.0.0,4.0.1)'

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="acmeweather" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>ACME Weather Binding</name>
<description>The ACME Weather Binding requests the ACME Weather Service to show the current temperature, humidity and
pressure.</description>
</binding:binding>

View File

@ -53,6 +53,7 @@ Fragment-Host: org.openhab.core.config.discovery.mdns
javax.jmdns;version='[3.5.8,3.5.9)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.discovery;version='[4.0.0,4.0.1)',\
@ -61,5 +62,4 @@ Fragment-Host: org.openhab.core.config.discovery.mdns
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.io.transport.mdns;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.thing;version='[4.0.0,4.0.1)'

View File

@ -56,6 +56,7 @@ Fragment-Host: org.openhab.core.config.discovery
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.discovery;version='[4.0.0,4.0.1)',\
@ -64,5 +65,4 @@ Fragment-Host: org.openhab.core.config.discovery
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.thing.xml;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.thing.xml;version='[4.0.0,4.0.1)'

View File

@ -53,6 +53,7 @@ Fragment-Host: org.openhab.core.config.discovery.usbserial.linuxsysfs
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.discovery;version='[4.0.0,4.0.1)',\
@ -61,5 +62,4 @@ Fragment-Host: org.openhab.core.config.discovery.usbserial.linuxsysfs
org.openhab.core.config.discovery.usbserial.linuxsysfs.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.thing;version='[4.0.0,4.0.1)'

View File

@ -62,6 +62,7 @@ Provide-Capability: \
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.discovery;version='[4.0.0,4.0.1)',\
@ -69,5 +70,4 @@ Provide-Capability: \
org.openhab.core.config.discovery.usbserial.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.thing;version='[4.0.0,4.0.1)'

View File

@ -48,8 +48,8 @@ Fragment-Host: org.openhab.core.config.dispatch
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.dispatch;version='[4.0.0,4.0.1)',\
org.openhab.core.config.dispatch.tests;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.test;version='[4.0.0,4.0.1)'

View File

@ -45,6 +45,7 @@ Fragment-Host: org.openhab.core.io.rest.core
org.glassfish.hk2.external.javax.inject;version='[2.4.0,2.4.1)',\
tech.units.indriya;version='[2.1.2,2.1.3)',\
uom-lib-common;version='[2.1.0,2.1.1)',\
com.fasterxml.woodstox.woodstox-core;version='[6.2.6,6.2.7)',\
org.apache.cxf.cxf-core;version='[3.4.5,3.4.6)',\
org.apache.cxf.cxf-rt-frontend-jaxrs;version='[3.4.5,3.4.6)',\
org.apache.cxf.cxf-rt-rs-client;version='[3.4.5,3.4.6)',\
@ -84,9 +85,11 @@ Fragment-Host: org.openhab.core.io.rest.core
org.ops4j.pax.web.pax-web-spi;version='[7.3.25,7.3.26)',\
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
junit-jupiter-params;version='[5.8.1,5.8.2)',\
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
junit-jupiter-params;version='[5.8.1,5.8.2)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.discovery;version='[4.0.0,4.0.1)',\
@ -98,7 +101,4 @@ Fragment-Host: org.openhab.core.io.rest.core
org.openhab.core.semantics;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
com.fasterxml.woodstox.woodstox-core;version='[6.4.0,6.4.1)'
org.openhab.core.transform;version='[4.0.0,4.0.1)'

View File

@ -75,6 +75,18 @@ Fragment-Host: org.openhab.core.model.core
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.apache.log4j;version='[1.2.19,1.2.20)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.audio;version='[4.0.0,4.0.1)',\
org.openhab.core.automation;version='[4.0.0,4.0.1)',\
@ -99,15 +111,4 @@ Fragment-Host: org.openhab.core.model.core
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
org.openhab.core.voice;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)'
org.openhab.core.voice;version='[4.0.0,4.0.1)'

View File

@ -72,6 +72,18 @@ Fragment-Host: org.openhab.core.model.item
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.apache.log4j;version='[1.2.19,1.2.20)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.audio;version='[4.0.0,4.0.1)',\
org.openhab.core.automation;version='[4.0.0,4.0.1)',\
@ -97,15 +109,4 @@ Fragment-Host: org.openhab.core.model.item
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
org.openhab.core.voice;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)'
org.openhab.core.voice;version='[4.0.0,4.0.1)'

View File

@ -73,6 +73,18 @@ Fragment-Host: org.openhab.core.model.rule.runtime
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.apache.log4j;version='[1.2.19,1.2.20)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.audio;version='[4.0.0,4.0.1)',\
org.openhab.core.automation;version='[4.0.0,4.0.1)',\
@ -98,16 +110,5 @@ Fragment-Host: org.openhab.core.model.rule.runtime
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
org.openhab.core.voice;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)'
org.openhab.core.voice;version='[4.0.0,4.0.1)'
-runblacklist: bnd.identity;id='jakarta.activation-api'

View File

@ -75,6 +75,18 @@ Fragment-Host: org.openhab.core.model.script
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.apache.log4j;version='[1.2.19,1.2.20)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.audio;version='[4.0.0,4.0.1)',\
org.openhab.core.automation;version='[4.0.0,4.0.1)',\
@ -99,15 +111,4 @@ Fragment-Host: org.openhab.core.model.script
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
org.openhab.core.voice;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)'
org.openhab.core.voice;version='[4.0.0,4.0.1)'

View File

@ -81,6 +81,18 @@ Fragment-Host: org.openhab.core.model.thing
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.apache.log4j;version='[1.2.19,1.2.20)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.audio;version='[4.0.0,4.0.1)',\
org.openhab.core.automation;version='[4.0.0,4.0.1)',\
@ -110,15 +122,4 @@ Fragment-Host: org.openhab.core.model.thing
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.thing.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.transform;version='[4.0.0,4.0.1)',\
org.openhab.core.voice;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
io.github.classgraph;version='[4.8.149,4.8.150)',\
org.eclipse.equinox.common;version='[3.16.200,3.16.201)',\
org.eclipse.xtend.lib;version='[2.29.0,2.29.1)',\
org.eclipse.xtend.lib.macro;version='[2.29.0,2.29.1)',\
org.eclipse.xtext;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.common.types;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.util;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase;version='[2.29.0,2.29.1)',\
org.eclipse.xtext.xbase.lib;version='[2.29.0,2.29.1)',\
org.objectweb.asm;version='[9.4.0,9.4.1)'
org.openhab.core.voice;version='[4.0.0,4.0.1)'

View File

@ -5,7 +5,7 @@ Fragment-Host: org.openhab.core.thing.xml
-runrequires: \
bnd.identity;id='org.openhab.core.thing.xml.tests',\
bnd.identity;id='org.openhab.core.binding.xml'
bnd.identity;id='org.openhab.core.addon.xml'
#
# done
@ -49,13 +49,13 @@ Fragment-Host: org.openhab.core.thing.xml
ch.qos.logback.classic;version='[1.2.11,1.2.12)',\
ch.qos.logback.core;version='[1.2.11,1.2.12)',\
biz.aQute.tester.junit-platform;version='[6.4.0,6.4.1)',\
com.google.gson;version='[2.9.1,2.9.2)',\
org.openhab.core;version='[4.0.0,4.0.1)',\
org.openhab.core.binding.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.addon.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.config.core;version='[4.0.0,4.0.1)',\
org.openhab.core.config.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.io.console;version='[4.0.0,4.0.1)',\
org.openhab.core.test;version='[4.0.0,4.0.1)',\
org.openhab.core.thing;version='[4.0.0,4.0.1)',\
org.openhab.core.thing.xml;version='[4.0.0,4.0.1)',\
org.openhab.core.thing.xml.tests;version='[4.0.0,4.0.1)',\
com.google.gson;version='[2.9.1,2.9.2)'
org.openhab.core.thing.xml.tests;version='[4.0.0,4.0.1)'

View File

@ -24,7 +24,7 @@
<module>org.openhab.core.automation.module.timer.tests</module>
<module>org.openhab.core.automation.module.script.tests</module>
<module>org.openhab.core.automation.integration.tests</module>
<module>org.openhab.core.binding.xml.tests</module>
<module>org.openhab.core.addon.xml.tests</module>
<module>org.openhab.core.config.core.tests</module>
<module>org.openhab.core.config.discovery.mdns.tests</module>
<module>org.openhab.core.config.discovery.tests</module>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="${bindingId}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>${bindingIdCamelCase} Binding</name>
<description>This is the binding for ${bindingIdCamelCase}.</description>
</addon:addon>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="${bindingId}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>${bindingIdCamelCase} Binding</name>
<description>This is the binding for ${bindingIdCamelCase}.</description>
</binding:binding>

View File

@ -51,7 +51,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>
</dependency>
<dependency>

View File

@ -19,7 +19,7 @@ import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.binding.xml.internal.BindingInfoXmlResult;
import org.openhab.core.addon.xml.internal.AddonInfoXmlResult;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.thing.xml.internal.ChannelGroupTypeXmlResult;
import org.openhab.core.thing.xml.internal.ChannelTypeXmlResult;
@ -35,8 +35,8 @@ import com.google.gson.JsonObject;
@NonNullByDefault
public class BundleInfo {
private String bindingId = "";
private @Nullable BindingInfoXmlResult bindingInfoXml;
private String addonId = "";
private @Nullable AddonInfoXmlResult addonInfoXml;
private List<ConfigDescription> configDescriptions = new ArrayList<>(5);
private List<ChannelGroupTypeXmlResult> channelGroupTypesXml = new ArrayList<>(5);
private List<ChannelTypeXmlResult> channelTypesXml = new ArrayList<>(5);
@ -44,20 +44,20 @@ public class BundleInfo {
private List<JsonObject> moduleTypesJson = new ArrayList<>(5);
private List<JsonObject> ruleTemplateJson = new ArrayList<>(5);
public String getBindingId() {
return bindingId;
public String getAddonId() {
return addonId;
}
public void setBindingId(String bindingId) {
this.bindingId = bindingId;
public void setAddonId(String addonId) {
this.addonId = addonId;
}
public @Nullable BindingInfoXmlResult getBindingInfoXml() {
return bindingInfoXml;
public @Nullable AddonInfoXmlResult getAddonInfoXml() {
return addonInfoXml;
}
public void setBindingInfoXml(BindingInfoXmlResult bindingInfo) {
this.bindingInfoXml = bindingInfo;
public void setAddonInfoXml(AddonInfoXmlResult addonInfo) {
this.addonInfoXml = addonInfo;
}
public List<ConfigDescription> getConfigDescriptions() {

View File

@ -25,8 +25,8 @@ import java.util.stream.StreamSupport;
import org.apache.maven.plugin.logging.Log;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.binding.xml.internal.BindingInfoReader;
import org.openhab.core.binding.xml.internal.BindingInfoXmlResult;
import org.openhab.core.addon.xml.internal.AddonInfoReader;
import org.openhab.core.addon.xml.internal.AddonInfoXmlResult;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.xml.internal.ConfigDescriptionReader;
import org.openhab.core.thing.xml.internal.ChannelGroupTypeXmlResult;
@ -58,7 +58,7 @@ public class BundleInfoReader {
public BundleInfo readBundleInfo(Path ohinfPath) throws IOException {
BundleInfo bundleInfo = new BundleInfo();
readBindingInfo(ohinfPath, bundleInfo);
readAddonInfo(ohinfPath, bundleInfo);
readConfigInfo(ohinfPath, bundleInfo);
readThingInfo(ohinfPath, bundleInfo);
readModuleTypeInfo(ohinfPath, bundleInfo);
@ -73,16 +73,16 @@ public class BundleInfoReader {
: Stream.of();
}
private void readBindingInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOException {
BindingInfoReader reader = new BindingInfoReader();
try (Stream<Path> xmlPathStream = xmlPathStream(ohinfPath, "binding")) {
private void readAddonInfo(Path ohinfPath, BundleInfo bundleInfo) throws IOException {
AddonInfoReader reader = new AddonInfoReader();
try (Stream<Path> xmlPathStream = xmlPathStream(ohinfPath, "addon")) {
xmlPathStream.forEach(path -> {
log.info("Reading: " + path);
try {
BindingInfoXmlResult bindingInfoXml = reader.readFromXML(path.toUri().toURL());
AddonInfoXmlResult bindingInfoXml = reader.readFromXML(path.toUri().toURL());
if (bindingInfoXml != null) {
bundleInfo.setBindingId(bindingInfoXml.getBindingInfo().getUID());
bundleInfo.setBindingInfoXml(bindingInfoXml);
bundleInfo.setAddonId(bindingInfoXml.addonInfo().getId());
bundleInfo.setAddonInfoXml(bindingInfoXml);
}
} catch (ConversionException | MalformedURLException e) {
log.warn("Exception while reading binding info from: " + path, e);
@ -119,20 +119,20 @@ public class BundleInfoReader {
if (type instanceof ThingTypeXmlResult) {
ThingTypeXmlResult result = (ThingTypeXmlResult) type;
bundleInfo.getThingTypesXml().add(result);
if (bundleInfo.getBindingId().isBlank()) {
bundleInfo.setBindingId(result.getUID().getBindingId());
if (bundleInfo.getAddonId().isBlank()) {
bundleInfo.setAddonId(result.getUID().getBindingId());
}
} else if (type instanceof ChannelGroupTypeXmlResult) {
ChannelGroupTypeXmlResult result = (ChannelGroupTypeXmlResult) type;
bundleInfo.getChannelGroupTypesXml().add(result);
if (bundleInfo.getBindingId().isBlank()) {
bundleInfo.setBindingId(result.getUID().getBindingId());
if (bundleInfo.getAddonId().isBlank()) {
bundleInfo.setAddonId(result.getUID().getBindingId());
}
} else if (type instanceof ChannelTypeXmlResult) {
ChannelTypeXmlResult result = (ChannelTypeXmlResult) type;
bundleInfo.getChannelTypesXml().add(result);
if (bundleInfo.getBindingId().isBlank()) {
bundleInfo.setBindingId(result.toChannelType().getUID().getBindingId());
if (bundleInfo.getAddonId().isBlank()) {
bundleInfo.setAddonId(result.toChannelType().getUID().getBindingId());
}
}
}

View File

@ -86,7 +86,7 @@ public class GenerateDefaultTranslationsMojo extends AbstractI18nMojo {
}
private String propertiesFileName(BundleInfo bundleInfo) {
String name = bundleInfo.getBindingId();
String name = bundleInfo.getAddonId();
if (name.isEmpty()) {
Optional<ConfigDescription> optional = bundleInfo.getConfigDescriptions().stream().findFirst();
if (optional.isPresent()) {

View File

@ -25,8 +25,8 @@ import java.util.stream.Stream;
import java.util.stream.Stream.Builder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.binding.BindingInfo;
import org.openhab.core.binding.xml.internal.BindingInfoXmlResult;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.addon.xml.internal.AddonInfoXmlResult;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionParameter;
import org.openhab.core.config.core.ConfigDescriptionParameterGroup;
@ -54,14 +54,14 @@ public class XmlToTranslationsConverter {
private static final Pattern OPTION_ESCAPE_PATTERN = Pattern.compile("([ :=])");
public Translations convert(BundleInfo bundleInfo) {
String bindingId = bundleInfo.getBindingId();
return bindingId.isBlank() ? configTranslations(bundleInfo) : bindingTranslations(bundleInfo);
String addonId = bundleInfo.getAddonId();
return addonId.isBlank() ? configTranslations(bundleInfo) : addonTranslations(bundleInfo);
}
private Translations bindingTranslations(BundleInfo bundleInfo) {
private Translations addonTranslations(BundleInfo bundleInfo) {
return Translations.translations( //
bindingSection(bundleInfo), //
bindingConfigSection(bundleInfo), //
addonSection(bundleInfo), //
addonConfigSection(bundleInfo), //
thingTypesSection(bundleInfo), //
thingTypesConfigSection(bundleInfo), //
channelGroupTypesSection(bundleInfo), //
@ -85,39 +85,39 @@ public class XmlToTranslationsConverter {
return Translations.translations(section(groupsBuilder.build()));
}
private TranslationsSection bindingSection(BundleInfo bundleInfo) {
String header = "binding";
BindingInfoXmlResult bindingInfoXml = bundleInfo.getBindingInfoXml();
if (bindingInfoXml == null) {
private TranslationsSection addonSection(BundleInfo bundleInfo) {
String header = "add-on";
AddonInfoXmlResult addonInfoXml = bundleInfo.getAddonInfoXml();
if (addonInfoXml == null) {
return section(header);
}
BindingInfo bindingInfo = bindingInfoXml.getBindingInfo();
String bindingId = bundleInfo.getBindingId();
AddonInfo addonInfo = addonInfoXml.addonInfo();
String addonId = bundleInfo.getAddonId();
String keyPrefix = String.format("binding.%s", bindingId);
String keyPrefix = String.format("addon.%s", addonId);
return section(header, group( //
entry(String.format("%s.name", keyPrefix), bindingInfo.getName()),
entry(String.format("%s.description", keyPrefix), bindingInfo.getDescription()) //
entry(String.format("%s.name", keyPrefix), addonInfo.getName()),
entry(String.format("%s.description", keyPrefix), addonInfo.getDescription()) //
));
}
private TranslationsSection bindingConfigSection(BundleInfo bundleInfo) {
String header = "binding config";
BindingInfoXmlResult bindingInfoXml = bundleInfo.getBindingInfoXml();
if (bindingInfoXml == null) {
private TranslationsSection addonConfigSection(BundleInfo bundleInfo) {
String header = "add-on config";
AddonInfoXmlResult addonInfoXml = bundleInfo.getAddonInfoXml();
if (addonInfoXml == null) {
return section(header);
}
ConfigDescription bindingConfig = bindingInfoXml.getConfigDescription();
if (bindingConfig == null) {
ConfigDescription addonConfig = addonInfoXml.configDescription();
if (addonConfig == null) {
return section(header);
}
String keyPrefix = String.format("binding.config.%s", bundleInfo.getBindingId());
String keyPrefix = String.format("addon.config.%s", bundleInfo.getAddonId());
return section(header,
Stream.concat(configDescriptionGroupParameters(keyPrefix, bindingConfig.getParameterGroups()),
configDescriptionParameters(keyPrefix, bindingConfig.getParameters())));
Stream.concat(configDescriptionGroupParameters(keyPrefix, addonConfig.getParameterGroups()),
configDescriptionParameters(keyPrefix, addonConfig.getParameters())));
}
private TranslationsSection thingTypesSection(BundleInfo bundleInfo) {
@ -127,8 +127,8 @@ public class XmlToTranslationsConverter {
return section(header);
}
String bindingId = bundleInfo.getBindingId();
String keyPrefix = String.format("thing-type.%s", bindingId);
String addonId = bundleInfo.getAddonId();
String keyPrefix = String.format("thing-type.%s", addonId);
return section(header, thingTypesXml.stream().map(thingTypeXml -> {
ThingType thingType = thingTypeXml.toThingType();
@ -216,7 +216,7 @@ public class XmlToTranslationsConverter {
return section(header);
}
String keyPrefix = String.format("channel-group-type.%s", bundleInfo.getBindingId());
String keyPrefix = String.format("channel-group-type.%s", bundleInfo.getAddonId());
return section(header, channelGroupTypesXml.stream().map(ChannelGroupTypeXmlResult::toChannelGroupType)
.map(channelGroupType -> {
@ -258,7 +258,7 @@ public class XmlToTranslationsConverter {
return section(header);
}
String keyPrefix = String.format("channel-type.%s", bundleInfo.getBindingId());
String keyPrefix = String.format("channel-type.%s", bundleInfo.getAddonId());
return section(header, channelTypesXml.stream().map(channelTypeXml -> {
ChannelType channelType = channelTypeXml.toChannelType();

View File

@ -21,7 +21,7 @@ import java.nio.file.Path;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.core.binding.xml.internal.BindingInfoXmlResult;
import org.openhab.core.addon.xml.internal.AddonInfoXmlResult;
/**
* Tests {@link BundleInfoReader}.
@ -36,15 +36,15 @@ public class BundleInfoReaderTest {
BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog());
BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmeweather.bundle/OH-INF"));
BindingInfoXmlResult bindingInfoXml = bundleInfo.getBindingInfoXml();
assertThat(bindingInfoXml, is(notNullValue()));
if (bindingInfoXml != null) {
assertThat(bindingInfoXml.getBindingInfo().getName(), is("ACME Weather Binding"));
assertThat(bindingInfoXml.getBindingInfo().getDescription(),
AddonInfoXmlResult addonInfoXml = bundleInfo.getAddonInfoXml();
assertThat(addonInfoXml, is(notNullValue()));
if (addonInfoXml != null) {
assertThat(addonInfoXml.addonInfo().getName(), is("ACME Weather Binding"));
assertThat(addonInfoXml.addonInfo().getDescription(),
is("ACME Weather - Current weather and forecasts in your city."));
}
assertThat(bundleInfo.getBindingId(), is("acmeweather"));
assertThat(bundleInfo.getAddonId(), is("acmeweather"));
assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(1));
assertThat(bundleInfo.getChannelTypesXml().size(), is(2));
assertThat(bundleInfo.getConfigDescriptions().size(), is(1));
@ -58,8 +58,8 @@ public class BundleInfoReaderTest {
BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog());
BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/acmetts.bundle/OH-INF"));
assertThat(bundleInfo.getBindingInfoXml(), is(nullValue()));
assertThat(bundleInfo.getBindingId(), is(""));
assertThat(bundleInfo.getAddonInfoXml(), is(nullValue()));
assertThat(bundleInfo.getAddonId(), is(""));
assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(0));
assertThat(bundleInfo.getChannelTypesXml().size(), is(0));
assertThat(bundleInfo.getConfigDescriptions().size(), is(1));
@ -73,8 +73,8 @@ public class BundleInfoReaderTest {
BundleInfoReader reader = new BundleInfoReader(new SystemStreamLog());
BundleInfo bundleInfo = reader.readBundleInfo(Path.of("src/test/resources/infoless.bundle/OH-INF"));
assertThat(bundleInfo.getBindingInfoXml(), is(nullValue()));
assertThat(bundleInfo.getBindingId(), is(""));
assertThat(bundleInfo.getAddonInfoXml(), is(nullValue()));
assertThat(bundleInfo.getAddonId(), is(""));
assertThat(bundleInfo.getChannelGroupTypesXml().size(), is(0));
assertThat(bundleInfo.getChannelTypesXml().size(), is(0));
assertThat(bundleInfo.getConfigDescriptions().size(), is(0));

View File

@ -123,7 +123,7 @@ public class GenerateDefaultTranslationsMojoTest {
}
@Test
public void addMissingBindingTranslationsWithoutI18nPath() throws IOException, MojoFailureException {
public void addMissingAddonTranslationsWithoutI18nPath() throws IOException, MojoFailureException {
copyPath(WEATHER_RESOURCES_PATH, tempPath);
deleteTempI18nPath();
@ -134,7 +134,7 @@ public class GenerateDefaultTranslationsMojoTest {
}
@Test
public void addMissingBindingTranslationsNoChanges() throws IOException, MojoFailureException {
public void addMissingAddonTranslationsNoChanges() throws IOException, MojoFailureException {
copyPath(WEATHER_RESOURCES_PATH, tempPath);
mojo.setGenerationMode(ADD_MISSING_TRANSLATIONS);

View File

@ -42,10 +42,10 @@ public class PropertiesToTranslationsConverterTest {
assertThat(translations.keysStream().count(), is(44L));
String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator()));
assertThat(lines, containsString("# binding"));
assertThat(lines, containsString("binding.acmeweather.name = ACME Weather Binding"));
assertThat(lines, containsString("# add-on"));
assertThat(lines, containsString("addon.acmeweather.name = ACME Weather Binding"));
assertThat(lines, containsString(
"binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city."));
"addon.acmeweather.description = ACME Weather - Current weather and forecasts in your city."));
assertThat(lines, containsString(
"channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial)."));
assertThat(lines, containsString(

View File

@ -45,10 +45,10 @@ public class XmlToTranslationsConverterTest {
assertThat(translations.keysStream().count(), is(34L));
String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator()));
assertThat(lines, containsString("# binding"));
assertThat(lines, containsString("binding.acmeweather.name = ACME Weather Binding"));
assertThat(lines, containsString("# add-on"));
assertThat(lines, containsString("addon.acmeweather.name = ACME Weather Binding"));
assertThat(lines, containsString(
"binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city."));
"addon.acmeweather.description = ACME Weather - Current weather and forecasts in your city."));
assertThat(lines, containsString(
"channel-group-type.acmeweather.forecast.channel.minTemperature.description = Minimum forecasted temperature in degrees Celsius (metric) or Fahrenheit (imperial)."));
assertThat(lines, containsString(

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="acmeweather" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>ACME Weather Binding</name>
<description>ACME Weather - Current weather and forecasts in your city.</description>
</addon:addon>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="acmeweather" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>ACME Weather Binding</name>
<description>ACME Weather - Current weather and forecasts in your city.</description>
</binding:binding>

View File

@ -1,7 +1,7 @@
# binding
# add-on
binding.acmeweather.name = ACME Weather Binding
binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city.
addon.acmeweather.name = ACME Weather Binding
addon.acmeweather.description = ACME Weather - Current weather and forecasts in your city.
# thing types

View File

@ -1,7 +1,7 @@
# binding
# add-on
binding.acmeweather.name = ACME Weather Binding
binding.acmeweather.description = ACME Weather - Current weather and forecasts in your city.
addon.acmeweather.name = ACME Weather Binding
addon.acmeweather.description = ACME Weather - Current weather and forecasts in your city.
# thing types

View File

@ -1,7 +1,7 @@
# binding
# add-on
binding.acmeweather.name = ACME Wetter Binding
binding.acmeweather.description = ACME Wetter - Aktuelles Wetter und Prognosen in Ihrer Stadt.
addon.acmeweather.name = ACME Wetter Binding
addon.acmeweather.description = ACME Wetter - Aktuelles Wetter und Prognosen in Ihrer Stadt.
# thing types