AddonInfo extensions (#3865)
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>pull/3899/head
parent
aa305d90d0
commit
cc9b70516a
|
@ -7,8 +7,9 @@
|
|||
<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:element name="addon" type="addon:addonInfo"/>
|
||||
|
||||
<xs:complexType name="addonInfo">
|
||||
<xs:sequence>
|
||||
<xs:element name="type" type="addon:addonType"/>
|
||||
<xs:element name="name" type="xs:string"/>
|
||||
|
@ -28,6 +29,7 @@
|
|||
<xs:element name="config-description" type="config-description:configDescription"/>
|
||||
<xs:element name="config-description-ref" type="config-description:configDescriptionRef"/>
|
||||
</xs:choice>
|
||||
<xs:element name="discovery-methods" type="addon:discoveryMethodsType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="id" type="config-description:idRestrictionPattern" use="required">
|
||||
<xs:annotation>
|
||||
|
@ -35,7 +37,6 @@
|
|||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:simpleType name="addonType">
|
||||
<xs:restriction base="xs:string">
|
||||
|
@ -80,4 +81,31 @@
|
|||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="discoveryMethodsType">
|
||||
<xs:sequence>
|
||||
<xs:element type="addon:discoveryMethodType" name="discovery-method" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discoveryMethodType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="service-type"/>
|
||||
<xs:element type="xs:string" name="mdns-service-type" minOccurs="0"/>
|
||||
<xs:element type="addon:matchPropertiesType" name="match-properties" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="matchPropertiesType">
|
||||
<xs:sequence>
|
||||
<xs:element type="addon:matchPropertyType" name="match-property" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="matchPropertyType">
|
||||
<xs:sequence>
|
||||
<xs:element type="xs:string" name="name"/>
|
||||
<xs:element type="xs:string" name="regex"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xmlns:addon-info-list="https://openhab.org/schemas/addon-info-list/v1.0.0"
|
||||
targetNamespace="https://openhab.org/schemas/addon-info-list/v1.0.0">
|
||||
|
||||
<xs:import namespace="https://openhab.org/schemas/addon/v1.0.0" schemaLocation="addon-1.0.0.xsd"/>
|
||||
|
||||
<xs:element name="addon-info-list">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="addons" type="addon-info-list:addons"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="addons">
|
||||
<xs:sequence>
|
||||
<xs:element name="addon" type="addon:addonInfo" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* DTO for serialization of a suggested addon discovery method.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonDiscoveryMethod {
|
||||
private @NonNullByDefault({}) String serviceType;
|
||||
private @Nullable String mdnsServiceType;
|
||||
private @Nullable List<AddonMatchProperty> matchProperties;
|
||||
|
||||
public String getServiceType() {
|
||||
return serviceType.toLowerCase();
|
||||
}
|
||||
|
||||
public String getMdnsServiceType() {
|
||||
String mdnsServiceType = this.mdnsServiceType;
|
||||
return mdnsServiceType != null ? mdnsServiceType : "";
|
||||
}
|
||||
|
||||
public List<AddonMatchProperty> getMatchProperties() {
|
||||
List<AddonMatchProperty> matchProperties = this.matchProperties;
|
||||
return matchProperties != null ? matchProperties : List.of();
|
||||
}
|
||||
|
||||
public AddonDiscoveryMethod setServiceType(String serviceType) {
|
||||
this.serviceType = serviceType.toLowerCase();
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddonDiscoveryMethod setMdnsServiceType(@Nullable String mdnsServiceType) {
|
||||
this.mdnsServiceType = mdnsServiceType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddonDiscoveryMethod setMatchProperties(@Nullable List<AddonMatchProperty> matchProperties) {
|
||||
this.matchProperties = matchProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(serviceType, mdnsServiceType, matchProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AddonDiscoveryMethod other = (AddonDiscoveryMethod) obj;
|
||||
return Objects.equals(serviceType, other.serviceType) && Objects.equals(mdnsServiceType, other.mdnsServiceType)
|
||||
&& Objects.equals(matchProperties, other.matchProperties);
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
|
||||
private final String id;
|
||||
private final String type;
|
||||
private final String uid;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final @Nullable String connection;
|
||||
|
@ -44,10 +45,12 @@ public class AddonInfo implements Identifiable<String> {
|
|||
private final @Nullable String configDescriptionURI;
|
||||
private final String serviceId;
|
||||
private @Nullable String sourceBundle;
|
||||
private @Nullable List<AddonDiscoveryMethod> discoveryMethods;
|
||||
|
||||
private AddonInfo(String id, String type, String name, String description, @Nullable String connection,
|
||||
List<String> countries, @Nullable String configDescriptionURI, @Nullable String serviceId,
|
||||
@Nullable String sourceBundle) throws IllegalArgumentException {
|
||||
private AddonInfo(String id, String type, @Nullable String uid, String name, String description,
|
||||
@Nullable String connection, List<String> countries, @Nullable String configDescriptionURI,
|
||||
@Nullable String serviceId, @Nullable String sourceBundle,
|
||||
@Nullable List<AddonDiscoveryMethod> discoveryMethods) throws IllegalArgumentException {
|
||||
// mandatory fields
|
||||
if (id.isBlank()) {
|
||||
throw new IllegalArgumentException("The ID must neither be null nor empty!");
|
||||
|
@ -64,6 +67,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
}
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.uid = uid != null ? uid : type + Addon.ADDON_SEPARATOR + id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
|
||||
|
@ -73,6 +77,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
this.configDescriptionURI = configDescriptionURI;
|
||||
this.serviceId = Objects.requireNonNullElse(serviceId, type + "." + id);
|
||||
this.sourceBundle = sourceBundle;
|
||||
this.discoveryMethods = discoveryMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +87,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
*/
|
||||
@Override
|
||||
public String getUID() {
|
||||
return type + Addon.ADDON_SEPARATOR + id;
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,6 +147,11 @@ public class AddonInfo implements Identifiable<String> {
|
|||
return countries;
|
||||
}
|
||||
|
||||
public List<AddonDiscoveryMethod> getDiscoveryMethods() {
|
||||
List<AddonDiscoveryMethod> discoveryMethods = this.discoveryMethods;
|
||||
return discoveryMethods != null ? discoveryMethods : List.of();
|
||||
}
|
||||
|
||||
public static Builder builder(String id, String type) {
|
||||
return new Builder(id, type);
|
||||
}
|
||||
|
@ -154,6 +164,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
|
||||
private final String id;
|
||||
private final String type;
|
||||
private @Nullable String uid;
|
||||
private String name = "";
|
||||
private String description = "";
|
||||
private @Nullable String connection;
|
||||
|
@ -161,6 +172,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
private @Nullable String configDescriptionURI = "";
|
||||
private @Nullable String serviceId;
|
||||
private @Nullable String sourceBundle;
|
||||
private @Nullable List<AddonDiscoveryMethod> discoveryMethods;
|
||||
|
||||
private Builder(String id, String type) {
|
||||
this.id = id;
|
||||
|
@ -170,6 +182,7 @@ public class AddonInfo implements Identifiable<String> {
|
|||
private Builder(AddonInfo addonInfo) {
|
||||
this.id = addonInfo.id;
|
||||
this.type = addonInfo.type;
|
||||
this.uid = addonInfo.uid;
|
||||
this.name = addonInfo.name;
|
||||
this.description = addonInfo.description;
|
||||
this.connection = addonInfo.connection;
|
||||
|
@ -177,6 +190,12 @@ public class AddonInfo implements Identifiable<String> {
|
|||
this.configDescriptionURI = addonInfo.configDescriptionURI;
|
||||
this.serviceId = addonInfo.serviceId;
|
||||
this.sourceBundle = addonInfo.sourceBundle;
|
||||
this.discoveryMethods = addonInfo.discoveryMethods;
|
||||
}
|
||||
|
||||
public Builder withUID(@Nullable String uid) {
|
||||
this.uid = uid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withName(String name) {
|
||||
|
@ -219,6 +238,11 @@ public class AddonInfo implements Identifiable<String> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder withDiscoveryMethods(@Nullable List<AddonDiscoveryMethod> discoveryMethods) {
|
||||
this.discoveryMethods = discoveryMethods;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an {@link AddonInfo} from this builder
|
||||
*
|
||||
|
@ -226,8 +250,8 @@ public class AddonInfo implements Identifiable<String> {
|
|||
* @throws IllegalArgumentException if any of the information in this builder is invalid
|
||||
*/
|
||||
public AddonInfo build() throws IllegalArgumentException {
|
||||
return new AddonInfo(id, type, name, description, connection, countries, configDescriptionURI, serviceId,
|
||||
sourceBundle);
|
||||
return new AddonInfo(id, type, uid, name, description, connection, countries, configDescriptionURI,
|
||||
serviceId, sourceBundle, discoveryMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* DTO containing a list of {@code AddonInfo}
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonInfoList {
|
||||
protected @Nullable List<AddonInfo> addons;
|
||||
|
||||
public List<AddonInfo> getAddons() {
|
||||
List<AddonInfo> addons = this.addons;
|
||||
return addons != null ? addons : List.of();
|
||||
}
|
||||
|
||||
public AddonInfoList setAddons(@Nullable List<AddonInfo> addons) {
|
||||
this.addons = addons;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -31,15 +31,15 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
public interface AddonInfoProvider {
|
||||
|
||||
/**
|
||||
* Returns the binding information for the specified binding ID and locale (language),
|
||||
* Returns the binding information for the specified binding UID 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 uid the UID 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)
|
||||
*/
|
||||
@Nullable
|
||||
AddonInfo getAddonInfo(@Nullable String id, @Nullable Locale locale);
|
||||
AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale);
|
||||
|
||||
/**
|
||||
* Returns all binding information in the specified locale (language) this provider contains.
|
||||
|
|
|
@ -13,10 +13,13 @@
|
|||
package org.openhab.core.addon;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
@ -44,34 +47,90 @@ public class AddonInfoRegistry {
|
|||
addonInfoProviders.add(addonInfoProvider);
|
||||
}
|
||||
|
||||
protected void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
|
||||
public 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
|
||||
* Returns the add-on information for the specified add-on UID, or {@code null} if no add-on information could be
|
||||
* found.
|
||||
*
|
||||
* @param id the ID to be looked
|
||||
* @param uid the UID to be looked
|
||||
* @return a add-on information object (could be null)
|
||||
*/
|
||||
public @Nullable AddonInfo getAddonInfo(String id) {
|
||||
return getAddonInfo(id, null);
|
||||
public @Nullable AddonInfo getAddonInfo(String uid) {
|
||||
return getAddonInfo(uid, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the add-on information for the specified add-on ID and locale (language),
|
||||
* Returns the add-on information for the specified add-on UID and locale (language),
|
||||
* or {@code null} if no add-on information could be found.
|
||||
* <p>
|
||||
* If more than one provider provides information for the specified add-on UID and locale,
|
||||
* it returns a new {@link AddonInfo} containing merged information from all such providers.
|
||||
*
|
||||
* @param id the ID to be looked for
|
||||
* @param uid the UID 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);
|
||||
public @Nullable AddonInfo getAddonInfo(String uid, @Nullable Locale locale) {
|
||||
return addonInfoProviders.stream().map(p -> p.getAddonInfo(uid, locale)).filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(a -> a == null ? "" : a.getUID(),
|
||||
Collectors.collectingAndThen(Collectors.reducing(mergeAddonInfos), Optional::get)))
|
||||
.get(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BinaryOperator} to merge the field values from two {@link AddonInfo} objects into a third such object.
|
||||
* <p>
|
||||
* If the first object has a non-null field value the result object takes the first value, or if the second object
|
||||
* has a non-null field value the result object takes the second value. Otherwise the field remains null.
|
||||
*
|
||||
* @param a the first {@link AddonInfo} (could be null)
|
||||
* @param b the second {@link AddonInfo} (could be null)
|
||||
* @return a new {@link AddonInfo} containing the combined field values (could be null)
|
||||
*/
|
||||
private static BinaryOperator<@Nullable AddonInfo> mergeAddonInfos = (a, b) -> {
|
||||
if (a == null) {
|
||||
return b;
|
||||
} else if (b == null) {
|
||||
return a;
|
||||
}
|
||||
AddonInfo.Builder builder = AddonInfo.builder(a);
|
||||
if (a.getDescription().isEmpty()) {
|
||||
builder.withDescription(b.getDescription());
|
||||
}
|
||||
if (a.getConnection() == null && b.getConnection() != null) {
|
||||
builder.withConnection(b.getConnection());
|
||||
}
|
||||
Set<String> countries = new HashSet<>(a.getCountries());
|
||||
countries.addAll(b.getCountries());
|
||||
if (!countries.isEmpty()) {
|
||||
builder.withCountries(countries.stream().toList());
|
||||
}
|
||||
String aConfigDescriptionURI = a.getConfigDescriptionURI();
|
||||
if (aConfigDescriptionURI == null || aConfigDescriptionURI.isEmpty() && b.getConfigDescriptionURI() != null) {
|
||||
builder.withConfigDescriptionURI(b.getConfigDescriptionURI());
|
||||
}
|
||||
if (a.getSourceBundle() == null && b.getSourceBundle() != null) {
|
||||
builder.withSourceBundle(b.getSourceBundle());
|
||||
}
|
||||
String defaultServiceId = a.getType() + "." + a.getId();
|
||||
if (defaultServiceId.equals(a.getServiceId()) && !defaultServiceId.equals(b.getServiceId())) {
|
||||
builder.withServiceId(b.getServiceId());
|
||||
}
|
||||
String defaultUID = a.getType() + Addon.ADDON_SEPARATOR + a.getId();
|
||||
if (defaultUID.equals(a.getUID()) && !defaultUID.equals(b.getUID())) {
|
||||
builder.withUID(b.getUID());
|
||||
}
|
||||
Set<AddonDiscoveryMethod> discoveryMethods = new HashSet<>(a.getDiscoveryMethods());
|
||||
discoveryMethods.addAll(b.getDiscoveryMethods());
|
||||
if (!discoveryMethods.isEmpty()) {
|
||||
builder.withDiscoveryMethods(discoveryMethods.stream().toList());
|
||||
}
|
||||
return builder.build();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all add-on information this registry contains.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* 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.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* DTO for serialization of a property match regular expression.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonMatchProperty {
|
||||
private @NonNullByDefault({}) String name;
|
||||
private @NonNullByDefault({}) String regex;
|
||||
private transient @NonNullByDefault({}) Pattern pattern;
|
||||
|
||||
public AddonMatchProperty(String name, String regex) {
|
||||
this.name = name;
|
||||
this.regex = regex;
|
||||
this.pattern = null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Pattern getPattern() {
|
||||
Pattern pattern = this.pattern;
|
||||
if (pattern == null) {
|
||||
this.pattern = Pattern.compile(regex);
|
||||
}
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, regex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AddonMatchProperty other = (AddonMatchProperty) obj;
|
||||
return Objects.equals(name, other.name) && Objects.equals(regex, other.regex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 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.internal.xml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.openhab.core.config.core.xml.util.GenericUnmarshaller;
|
||||
import org.openhab.core.config.core.xml.util.NodeIterator;
|
||||
|
||||
import com.thoughtworks.xstream.converters.UnmarshallingContext;
|
||||
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
|
||||
|
||||
/**
|
||||
* The {@link AddonDiscoveryMethodConverter} is a concrete implementation of the {@code XStream} {@link Converter}
|
||||
* interface used to convert add-on discovery method information within an XML document into a
|
||||
* {@link AddonDiscoveryMethod} object.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonDiscoveryMethodConverter extends GenericUnmarshaller<AddonDiscoveryMethod> {
|
||||
|
||||
public AddonDiscoveryMethodConverter() {
|
||||
super(AddonDiscoveryMethod.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
|
||||
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
|
||||
NodeIterator nodeIterator = new NodeIterator(nodes);
|
||||
|
||||
String serviceType = requireNonEmpty((String) nodeIterator.nextValue("service-type", true),
|
||||
"Service type is null or empty");
|
||||
|
||||
String mdnsServiceType = (String) nodeIterator.nextValue("mdns-service-type", false);
|
||||
|
||||
Object object = nodeIterator.nextList("match-properties", false);
|
||||
List<AddonMatchProperty> matchProperties = !(object instanceof List<?> list) ? null
|
||||
: list.stream().filter(e -> (e instanceof AddonMatchProperty)).map(e -> ((AddonMatchProperty) e))
|
||||
.toList();
|
||||
|
||||
nodeIterator.assertEndOfType();
|
||||
|
||||
return new AddonDiscoveryMethod().setServiceType(serviceType).setMdnsServiceType(mdnsServiceType)
|
||||
.setMatchProperties(matchProperties);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* 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.internal.xml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonInfo;
|
||||
import org.openhab.core.addon.AddonInfoProvider;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.thoughtworks.xstream.XStreamException;
|
||||
import com.thoughtworks.xstream.converters.ConversionException;
|
||||
|
||||
/**
|
||||
* The {@link AddonInfoAddonsXmlProvider} reads all {@code userdata/addons/*.xml} files, each of which
|
||||
* should contain a list of {@code addon} elements, and convert their combined contents into a list
|
||||
* of {@link AddonInfo} objects can be accessed via the {@link AddonInfoProvider} interface.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = AddonInfoProvider.class, name = AddonInfoAddonsXmlProvider.SERVICE_NAME)
|
||||
public class AddonInfoAddonsXmlProvider implements AddonInfoProvider {
|
||||
|
||||
public static final String SERVICE_NAME = "addons-info-provider";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AddonInfoAddonsXmlProvider.class);
|
||||
private final String folder = OpenHAB.getUserDataFolder() + File.separator + "addons";
|
||||
private final Set<AddonInfo> addonInfos = new HashSet<>();
|
||||
|
||||
@Activate
|
||||
public AddonInfoAddonsXmlProvider() {
|
||||
initialize();
|
||||
testAddonDeveloperRegexSyntax();
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
addonInfos.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AddonInfo getAddonInfo(@Nullable String uid, @Nullable Locale locale) {
|
||||
return addonInfos.stream().filter(a -> a.getUID().equals(uid)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AddonInfo> getAddonInfos(@Nullable Locale locale) {
|
||||
return addonInfos;
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
AddonInfoListReader reader = new AddonInfoListReader();
|
||||
Stream.of(new File(folder).listFiles()).filter(f -> f.isFile() && f.getName().endsWith(".xml")).forEach(f -> {
|
||||
try {
|
||||
String xml = Files.readString(f.toPath());
|
||||
if (xml != null && !xml.isBlank()) {
|
||||
addonInfos.addAll(reader.readFromXML(xml).getAddons().stream().collect(Collectors.toSet()));
|
||||
} else {
|
||||
logger.warn("File '{}' contents are null or empty", f.getName());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("File '{}' could not be read", f.getName());
|
||||
} catch (ConversionException e) {
|
||||
logger.warn("File '{}' has invalid content", f.getName());
|
||||
} catch (XStreamException e) {
|
||||
logger.warn("File '{}' could not be deserialized", f.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* The openhab-addons Maven build process checks individual developer addon.xml contributions
|
||||
* against the 'addon-1.0.0.xsd' schema, but it can't check the discovery-method match-property
|
||||
* regex syntax. Invalid regexes do throw exceptions at run-time, but the log can't identify the
|
||||
* culprit addon. Ideally we need to add syntax checks to the Maven build; and this test is an
|
||||
* interim solution.
|
||||
*/
|
||||
private void testAddonDeveloperRegexSyntax() {
|
||||
List<String> patternErrors = new ArrayList<>();
|
||||
for (AddonInfo addonInfo : addonInfos) {
|
||||
for (AddonDiscoveryMethod discoveryMethod : addonInfo.getDiscoveryMethods()) {
|
||||
for (AddonMatchProperty matchProperty : discoveryMethod.getMatchProperties()) {
|
||||
try {
|
||||
matchProperty.getPattern();
|
||||
} catch (PatternSyntaxException e) {
|
||||
patternErrors.add(String.format(
|
||||
"Regex syntax error in org.openhab.%s.%s addon.xml => %s in \"%s\" position %d",
|
||||
addonInfo.getType(), addonInfo.getId(), e.getDescription(), e.getPattern(),
|
||||
e.getIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!patternErrors.isEmpty()) {
|
||||
logger.warn("The following errors were found:\n\t{}", String.join("\n\t", patternErrors));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import java.util.Map;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonInfo;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionBuilder;
|
||||
|
@ -37,6 +38,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader;
|
|||
* @author Michael Grammling - Initial contribution
|
||||
* @author Andre Fuechsel - Made author tag optional
|
||||
* @author Jan N. Klug - Refactored to cover all add-ons
|
||||
* @author Andrew Fiddian-Green - Added discovery methods
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonInfoConverter extends GenericUnmarshaller<AddonInfoXmlResult> {
|
||||
|
@ -107,6 +109,11 @@ public class AddonInfoConverter extends GenericUnmarshaller<AddonInfoXmlResult>
|
|||
|
||||
addonInfo.withConfigDescriptionURI(configDescriptionURI);
|
||||
|
||||
Object object = nodeIterator.nextList("discovery-methods", false);
|
||||
addonInfo.withDiscoveryMethods(!(object instanceof List<?> list) ? null
|
||||
: list.stream().filter(e -> (e instanceof AddonDiscoveryMethod)).map(e -> ((AddonDiscoveryMethod) e))
|
||||
.toList());
|
||||
|
||||
nodeIterator.assertEndOfType();
|
||||
|
||||
// create object
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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.internal.xml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.addon.AddonInfo;
|
||||
import org.openhab.core.addon.AddonInfoList;
|
||||
import org.openhab.core.config.core.xml.util.GenericUnmarshaller;
|
||||
import org.openhab.core.config.core.xml.util.NodeIterator;
|
||||
|
||||
import com.thoughtworks.xstream.converters.Converter;
|
||||
import com.thoughtworks.xstream.converters.UnmarshallingContext;
|
||||
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
|
||||
|
||||
/**
|
||||
* The {@link AddonInfoListConverter} is a concrete implementation of the {@code XStream} {@link Converter}
|
||||
* interface used to convert a list of add-on information within an XML document into a list of {@link AddonInfo}
|
||||
* objects.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonInfoListConverter extends GenericUnmarshaller<AddonInfoList> {
|
||||
|
||||
public AddonInfoListConverter() {
|
||||
super(AddonInfoList.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
|
||||
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
|
||||
NodeIterator nodeIterator = new NodeIterator(nodes);
|
||||
|
||||
Object object = nodeIterator.nextList("addons", false);
|
||||
List<AddonInfo> addons = (object instanceof List<?> list)
|
||||
? list.stream().filter(e -> e != null).filter(e -> (e instanceof AddonInfoXmlResult))
|
||||
.map(e -> (AddonInfoXmlResult) e).map(r -> r.addonInfo()).toList()
|
||||
: null;
|
||||
|
||||
nodeIterator.assertEndOfType();
|
||||
|
||||
return new AddonInfoList().setAddons(addons);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* 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.internal.xml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonInfoList;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameterGroup;
|
||||
import org.openhab.core.config.core.FilterCriteria;
|
||||
import org.openhab.core.config.core.xml.ConfigDescriptionConverter;
|
||||
import org.openhab.core.config.core.xml.ConfigDescriptionParameterConverter;
|
||||
import org.openhab.core.config.core.xml.ConfigDescriptionParameterGroupConverter;
|
||||
import org.openhab.core.config.core.xml.FilterCriteriaConverter;
|
||||
import org.openhab.core.config.core.xml.util.NodeAttributes;
|
||||
import org.openhab.core.config.core.xml.util.NodeAttributesConverter;
|
||||
import org.openhab.core.config.core.xml.util.NodeList;
|
||||
import org.openhab.core.config.core.xml.util.NodeListConverter;
|
||||
import org.openhab.core.config.core.xml.util.NodeValue;
|
||||
import org.openhab.core.config.core.xml.util.NodeValueConverter;
|
||||
import org.openhab.core.config.core.xml.util.XmlDocumentReader;
|
||||
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
|
||||
/**
|
||||
* The {@link AddonInfoListReader} reads XML documents, which contain the {@code addon} XML tag, and converts them to
|
||||
* a List of {@link AddonInfoXmlResult} objects.
|
||||
* <p>
|
||||
* This reader uses {@code XStream} and {@code StAX} to parse and convert the XML document.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonInfoListReader extends XmlDocumentReader<AddonInfoList> {
|
||||
|
||||
/**
|
||||
* The default constructor of this class.
|
||||
*/
|
||||
public AddonInfoListReader() {
|
||||
ClassLoader classLoader = AddonInfoListReader.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
super.setClassLoader(classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerConverters(XStream xstream) {
|
||||
xstream.registerConverter(new NodeAttributesConverter());
|
||||
xstream.registerConverter(new NodeListConverter());
|
||||
xstream.registerConverter(new NodeValueConverter());
|
||||
xstream.registerConverter(new AddonInfoListConverter());
|
||||
xstream.registerConverter(new AddonInfoConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionParameterConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionParameterGroupConverter());
|
||||
xstream.registerConverter(new FilterCriteriaConverter());
|
||||
xstream.registerConverter(new AddonDiscoveryMethodConverter());
|
||||
xstream.registerConverter(new AddonMatchPropertyConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerAliases(XStream xstream) {
|
||||
xstream.alias("addon-info-list", AddonInfoList.class);
|
||||
xstream.alias("addons", NodeList.class);
|
||||
xstream.alias("addon", AddonInfoXmlResult.class);
|
||||
xstream.alias("name", NodeValue.class);
|
||||
xstream.alias("description", NodeValue.class);
|
||||
xstream.alias("type", NodeValue.class);
|
||||
xstream.alias("connection", NodeValue.class);
|
||||
xstream.alias("countries", NodeValue.class);
|
||||
xstream.alias("config-description", ConfigDescription.class);
|
||||
xstream.alias("config-description-ref", NodeAttributes.class);
|
||||
xstream.alias("parameter", ConfigDescriptionParameter.class);
|
||||
xstream.alias("parameter-group", ConfigDescriptionParameterGroup.class);
|
||||
xstream.alias("options", NodeList.class);
|
||||
xstream.alias("option", NodeValue.class);
|
||||
xstream.alias("filter", List.class);
|
||||
xstream.alias("criteria", FilterCriteria.class);
|
||||
xstream.alias("service-id", NodeValue.class);
|
||||
xstream.alias("discovery-methods", NodeList.class);
|
||||
xstream.alias("discovery-method", AddonDiscoveryMethod.class);
|
||||
xstream.alias("service-type", NodeValue.class);
|
||||
xstream.alias("mdns-service-type", NodeValue.class);
|
||||
xstream.alias("match-properties", NodeList.class);
|
||||
xstream.alias("match-property", AddonMatchProperty.class);
|
||||
xstream.alias("regex", NodeValue.class);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ package org.openhab.core.addon.internal.xml;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameterGroup;
|
||||
|
@ -26,6 +28,7 @@ import org.openhab.core.config.core.xml.FilterCriteriaConverter;
|
|||
import org.openhab.core.config.core.xml.util.NodeAttributes;
|
||||
import org.openhab.core.config.core.xml.util.NodeAttributesConverter;
|
||||
import org.openhab.core.config.core.xml.util.NodeList;
|
||||
import org.openhab.core.config.core.xml.util.NodeListConverter;
|
||||
import org.openhab.core.config.core.xml.util.NodeValue;
|
||||
import org.openhab.core.config.core.xml.util.NodeValueConverter;
|
||||
import org.openhab.core.config.core.xml.util.XmlDocumentReader;
|
||||
|
@ -33,7 +36,7 @@ import org.openhab.core.config.core.xml.util.XmlDocumentReader;
|
|||
import com.thoughtworks.xstream.XStream;
|
||||
|
||||
/**
|
||||
* The {@link AddonInfoReader} reads XML documents, which contain the {@code binding} XML tag,
|
||||
* The {@link AddonInfoReader} reads XML documents, which contain the {@code addon} 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.
|
||||
|
@ -59,12 +62,15 @@ public class AddonInfoReader extends XmlDocumentReader<AddonInfoXmlResult> {
|
|||
@Override
|
||||
protected void registerConverters(XStream xstream) {
|
||||
xstream.registerConverter(new NodeAttributesConverter());
|
||||
xstream.registerConverter(new NodeListConverter());
|
||||
xstream.registerConverter(new NodeValueConverter());
|
||||
xstream.registerConverter(new AddonInfoConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionParameterConverter());
|
||||
xstream.registerConverter(new ConfigDescriptionParameterGroupConverter());
|
||||
xstream.registerConverter(new FilterCriteriaConverter());
|
||||
xstream.registerConverter(new AddonDiscoveryMethodConverter());
|
||||
xstream.registerConverter(new AddonMatchPropertyConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,5 +90,12 @@ public class AddonInfoReader extends XmlDocumentReader<AddonInfoXmlResult> {
|
|||
xstream.alias("filter", List.class);
|
||||
xstream.alias("criteria", FilterCriteria.class);
|
||||
xstream.alias("service-id", NodeValue.class);
|
||||
xstream.alias("discovery-methods", NodeList.class);
|
||||
xstream.alias("discovery-method", AddonDiscoveryMethod.class);
|
||||
xstream.alias("service-type", NodeValue.class);
|
||||
xstream.alias("mdns-service-type", NodeValue.class);
|
||||
xstream.alias("match-properties", NodeList.class);
|
||||
xstream.alias("match-property", AddonMatchProperty.class);
|
||||
xstream.alias("regex", NodeValue.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* 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.internal.xml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.openhab.core.config.core.xml.util.GenericUnmarshaller;
|
||||
import org.openhab.core.config.core.xml.util.NodeIterator;
|
||||
|
||||
import com.thoughtworks.xstream.converters.UnmarshallingContext;
|
||||
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
|
||||
|
||||
/**
|
||||
* The {@link AddonMatchPropertyConverter} is a concrete implementation of the {@code XStream} {@link Converter}
|
||||
* interface used to convert add-on discovery method match property information within an XML document into a
|
||||
* {@link AddonMatchProperty} object.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddonMatchPropertyConverter extends GenericUnmarshaller<AddonMatchProperty> {
|
||||
|
||||
public AddonMatchPropertyConverter() {
|
||||
super(AddonMatchProperty.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
|
||||
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
|
||||
NodeIterator nodeIterator = new NodeIterator(nodes);
|
||||
|
||||
String name = requireNonEmpty((String) nodeIterator.nextValue("name", true), "Name is null or empty");
|
||||
String regex = requireNonEmpty((String) nodeIterator.nextValue("regex", true), "Regex is null or empty");
|
||||
|
||||
nodeIterator.assertEndOfType();
|
||||
|
||||
return new AddonMatchProperty(name, regex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.addon.internal.xml.AddonInfoListReader;
|
||||
|
||||
/**
|
||||
* JUnit tests for {@link AddonInfoListReader}.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class AddonInfoListReaderTest {
|
||||
|
||||
// @formatter:off
|
||||
private final String testXml =
|
||||
"<addon-info-list><addons>"
|
||||
+ " <addon:addon id=\"groovyscripting\" 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>automation</type>"
|
||||
+ " <name>Groovy Scripting</name>"
|
||||
+ " <description>This adds a Groovy script engine.</description>"
|
||||
+ " <connection>none</connection>"
|
||||
+ " <discovery-methods>"
|
||||
+ " <discovery-method>"
|
||||
+ " <service-type>mdns</service-type>"
|
||||
+ " <mdns-service-type>_printer._tcp.local.</mdns-service-type>"
|
||||
+ " <match-properties>"
|
||||
+ " <match-property>"
|
||||
+ " <name>rp</name>"
|
||||
+ " <regex>.*</regex>"
|
||||
+ " </match-property>"
|
||||
+ " <match-property>"
|
||||
+ " <name>ty</name>"
|
||||
+ " <regex>hp (.*)</regex>"
|
||||
+ " </match-property>"
|
||||
+ " </match-properties>"
|
||||
+ " </discovery-method>"
|
||||
+ " <discovery-method>"
|
||||
+ " <service-type>upnp</service-type>"
|
||||
+ " <match-properties>"
|
||||
+ " <match-property>"
|
||||
+ " <name>modelName</name>"
|
||||
+ " <regex>Philips hue bridge</regex>"
|
||||
+ " </match-property>"
|
||||
+ " </match-properties>"
|
||||
+ " </discovery-method>"
|
||||
+ " </discovery-methods>"
|
||||
+ " </addon:addon>"
|
||||
+ "</addons></addon-info-list>";
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
void testAddonInfoListReader() {
|
||||
AddonInfoList addons = null;
|
||||
try {
|
||||
AddonInfoListReader reader = new AddonInfoListReader();
|
||||
addons = reader.readFromXML(testXml);
|
||||
} catch (Exception e) {
|
||||
fail(e);
|
||||
}
|
||||
assertNotNull(addons);
|
||||
List<AddonInfo> addonsInfos = addons.getAddons();
|
||||
assertEquals(1, addonsInfos.size());
|
||||
AddonInfo addon = addonsInfos.get(0);
|
||||
assertNotNull(addon);
|
||||
List<AddonDiscoveryMethod> discoveryMethods = addon.getDiscoveryMethods();
|
||||
assertNotNull(discoveryMethods);
|
||||
assertEquals(2, discoveryMethods.size());
|
||||
|
||||
AddonDiscoveryMethod method = discoveryMethods.get(0);
|
||||
assertNotNull(method);
|
||||
assertEquals("mdns", method.getServiceType());
|
||||
assertEquals("_printer._tcp.local.", method.getMdnsServiceType());
|
||||
List<AddonMatchProperty> matchProperties = method.getMatchProperties();
|
||||
assertNotNull(matchProperties);
|
||||
assertEquals(2, matchProperties.size());
|
||||
AddonMatchProperty property = matchProperties.get(0);
|
||||
assertNotNull(property);
|
||||
assertEquals("rp", property.getName());
|
||||
assertEquals(".*", property.getRegex());
|
||||
assertTrue(property.getPattern().matcher("the cat sat on the mat").matches());
|
||||
|
||||
method = discoveryMethods.get(1);
|
||||
assertNotNull(method);
|
||||
assertEquals("upnp", method.getServiceType());
|
||||
assertEquals("", method.getMdnsServiceType());
|
||||
matchProperties = method.getMatchProperties();
|
||||
assertNotNull(matchProperties);
|
||||
assertEquals(1, matchProperties.size());
|
||||
property = matchProperties.get(0);
|
||||
assertNotNull(property);
|
||||
assertEquals("modelName", property.getName());
|
||||
assertEquals("Philips hue bridge", property.getRegex());
|
||||
assertTrue(property.getPattern().matcher("Philips hue bridge").matches());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
|
||||
/**
|
||||
* JUnit test for the {@link AddonInfoRegistry} merge function.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class AddonInfoRegistryMergeTest {
|
||||
|
||||
private @Nullable AddonInfoProvider addonInfoProvider0;
|
||||
private @Nullable AddonInfoProvider addonInfoProvider1;
|
||||
private @Nullable AddonInfoProvider addonInfoProvider2;
|
||||
|
||||
@BeforeAll
|
||||
void beforeAll() {
|
||||
addonInfoProvider0 = createAddonInfoProvider0();
|
||||
addonInfoProvider1 = createAddonInfoProvider1();
|
||||
addonInfoProvider2 = createAddonInfoProvider2();
|
||||
}
|
||||
|
||||
private AddonInfoProvider createAddonInfoProvider0() {
|
||||
AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-zero")
|
||||
.withDescription("description-zero").build();
|
||||
AddonInfoProvider provider = mock(AddonInfoProvider.class);
|
||||
when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null);
|
||||
when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private AddonInfoProvider createAddonInfoProvider1() {
|
||||
AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("mdns")
|
||||
.setMdnsServiceType("_hue._tcp.local.");
|
||||
AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-one")
|
||||
.withDescription("description-one").withCountries("GB,NL").withConnection("local")
|
||||
.withDiscoveryMethods(List.of(discoveryMethod)).build();
|
||||
AddonInfoProvider provider = mock(AddonInfoProvider.class);
|
||||
when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null);
|
||||
when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private AddonInfoProvider createAddonInfoProvider2() {
|
||||
AddonDiscoveryMethod discoveryMethod = new AddonDiscoveryMethod().setServiceType("upnp")
|
||||
.setMatchProperties(List.of(new AddonMatchProperty("modelName", "Philips hue bridge")));
|
||||
AddonInfo addonInfo = AddonInfo.builder("hue", "binding").withName("name-two")
|
||||
.withDescription("description-two").withCountries("DE,FR").withSourceBundle("source-bundle")
|
||||
.withServiceId("service-id").withConfigDescriptionURI("http://www.openhab.org")
|
||||
.withDiscoveryMethods(List.of(discoveryMethod)).build();
|
||||
AddonInfoProvider provider = mock(AddonInfoProvider.class);
|
||||
when(provider.getAddonInfo(anyString(), any(Locale.class))).thenReturn(null);
|
||||
when(provider.getAddonInfo(anyString(), eq(null))).thenReturn(null);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), any(Locale.class))).thenReturn(addonInfo);
|
||||
when(provider.getAddonInfo(eq("binding-hue"), eq(null))).thenReturn(null);
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching a single addon-info from the registry with no merging.
|
||||
*/
|
||||
@Test
|
||||
void testGetOneAddonInfo() {
|
||||
AddonInfoRegistry registry = new AddonInfoRegistry();
|
||||
assertNotNull(addonInfoProvider0);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0));
|
||||
|
||||
AddonInfo addonInfo;
|
||||
addonInfo = registry.getAddonInfo("aardvark", Locale.US);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("aardvark", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", Locale.US);
|
||||
assertNotNull(addonInfo);
|
||||
|
||||
assertEquals("hue", addonInfo.getId());
|
||||
assertEquals("binding", addonInfo.getType());
|
||||
assertEquals("binding-hue", addonInfo.getUID());
|
||||
assertTrue(addonInfo.getName().startsWith("name-"));
|
||||
assertTrue(addonInfo.getDescription().startsWith("description-"));
|
||||
assertNull(addonInfo.getSourceBundle());
|
||||
assertNotEquals("local", addonInfo.getConnection());
|
||||
assertEquals(0, addonInfo.getCountries().size());
|
||||
assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI());
|
||||
assertEquals("binding.hue", addonInfo.getServiceId());
|
||||
assertEquals(0, addonInfo.getDiscoveryMethods().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching two addon-info's from the registry with merging.
|
||||
*/
|
||||
@Test
|
||||
void testMergeAddonInfos2() {
|
||||
AddonInfoRegistry registry = new AddonInfoRegistry();
|
||||
assertNotNull(addonInfoProvider0);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0));
|
||||
assertNotNull(addonInfoProvider1);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1));
|
||||
|
||||
AddonInfo addonInfo;
|
||||
addonInfo = registry.getAddonInfo("aardvark", Locale.US);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("aardvark", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", Locale.US);
|
||||
assertNotNull(addonInfo);
|
||||
|
||||
assertEquals("hue", addonInfo.getId());
|
||||
assertEquals("binding", addonInfo.getType());
|
||||
assertEquals("binding-hue", addonInfo.getUID());
|
||||
assertTrue(addonInfo.getName().startsWith("name-"));
|
||||
assertTrue(addonInfo.getDescription().startsWith("description-"));
|
||||
assertNull(addonInfo.getSourceBundle());
|
||||
assertEquals("local", addonInfo.getConnection());
|
||||
assertEquals(2, addonInfo.getCountries().size());
|
||||
assertNotEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI());
|
||||
assertEquals("binding.hue", addonInfo.getServiceId());
|
||||
assertEquals(1, addonInfo.getDiscoveryMethods().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching three addon-info's from the registry with full merging.
|
||||
*/
|
||||
@Test
|
||||
void testMergeAddonInfos3() {
|
||||
AddonInfoRegistry registry = new AddonInfoRegistry();
|
||||
assertNotNull(addonInfoProvider0);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider0));
|
||||
assertNotNull(addonInfoProvider1);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider1));
|
||||
assertNotNull(addonInfoProvider2);
|
||||
registry.addAddonInfoProvider(Objects.requireNonNull(addonInfoProvider2));
|
||||
|
||||
AddonInfo addonInfo;
|
||||
addonInfo = registry.getAddonInfo("aardvark", Locale.US);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("aardvark", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", null);
|
||||
assertNull(addonInfo);
|
||||
addonInfo = registry.getAddonInfo("binding-hue", Locale.US);
|
||||
assertNotNull(addonInfo);
|
||||
|
||||
assertEquals("hue", addonInfo.getId());
|
||||
assertEquals("binding", addonInfo.getType());
|
||||
assertEquals("binding-hue", addonInfo.getUID());
|
||||
assertTrue(addonInfo.getName().startsWith("name-"));
|
||||
assertTrue(addonInfo.getDescription().startsWith("description-"));
|
||||
assertEquals("source-bundle", addonInfo.getSourceBundle());
|
||||
assertEquals("local", addonInfo.getConnection());
|
||||
assertEquals(4, addonInfo.getCountries().size());
|
||||
assertEquals("http://www.openhab.org", addonInfo.getConfigDescriptionURI());
|
||||
assertEquals("service-id", addonInfo.getServiceId());
|
||||
assertEquals(2, addonInfo.getDiscoveryMethods().size());
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
import com.thoughtworks.xstream.XStreamException;
|
||||
import com.thoughtworks.xstream.converters.ConversionException;
|
||||
import com.thoughtworks.xstream.converters.Converter;
|
||||
import com.thoughtworks.xstream.io.xml.StaxDriver;
|
||||
|
@ -104,4 +105,18 @@ public abstract class XmlDocumentReader<@NonNull T> {
|
|||
public @Nullable T readFromXML(URL xmlURL) throws ConversionException {
|
||||
return (@Nullable T) xstream.fromXML(xmlURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the XML document containing a specific XML tag from the specified xml string and converts it to the
|
||||
* according object.
|
||||
*
|
||||
* @param xml a string containing the XML document to be read.
|
||||
* @return the conversion result object (could be null).
|
||||
* @throws XStreamException if the object cannot be deserialized.
|
||||
* @throws ConversionException if the specified document contains invalid content
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public @Nullable T readFromXML(String xml) throws ConversionException {
|
||||
return (@Nullable T) xstream.fromXML(xml);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,12 @@
|
|||
*/
|
||||
package org.openhab.core.addon.xml.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
@ -24,8 +28,10 @@ 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.addon.AddonDiscoveryMethod;
|
||||
import org.openhab.core.addon.AddonInfo;
|
||||
import org.openhab.core.addon.AddonInfoRegistry;
|
||||
import org.openhab.core.addon.AddonMatchProperty;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
|
@ -66,6 +72,31 @@ public class AddonInfoTest extends JavaOSGiTest {
|
|||
assertThat(addonInfo.getDescription(),
|
||||
is("The hue Binding integrates the Philips hue system. It allows to control hue lights."));
|
||||
assertThat(addonInfo.getName(), is("hue Binding"));
|
||||
|
||||
List<AddonDiscoveryMethod> discoveryMethods = addonInfo.getDiscoveryMethods();
|
||||
assertNotNull(discoveryMethods);
|
||||
assertEquals(2, discoveryMethods.size());
|
||||
|
||||
AddonDiscoveryMethod discoveryMethod = discoveryMethods.get(0);
|
||||
assertNotNull(discoveryMethod);
|
||||
assertEquals("mdns", discoveryMethod.getServiceType());
|
||||
assertEquals("_hue._tcp.local.", discoveryMethod.getMdnsServiceType());
|
||||
List<AddonMatchProperty> properties = discoveryMethod.getMatchProperties();
|
||||
assertNotNull(properties);
|
||||
assertEquals(0, properties.size());
|
||||
|
||||
discoveryMethod = discoveryMethods.get(1);
|
||||
assertNotNull(discoveryMethod);
|
||||
assertEquals("upnp", discoveryMethod.getServiceType());
|
||||
assertEquals("", discoveryMethod.getMdnsServiceType());
|
||||
properties = discoveryMethod.getMatchProperties();
|
||||
assertNotNull(properties);
|
||||
assertEquals(1, properties.size());
|
||||
AddonMatchProperty property = properties.get(0);
|
||||
assertNotNull(property);
|
||||
assertEquals("modelName", property.getName());
|
||||
assertEquals("Philips hue bridge", property.getRegex());
|
||||
assertTrue(property.getPattern().matcher("Philips hue bridge").matches());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -30,4 +30,21 @@
|
|||
|
||||
</config-description>
|
||||
|
||||
<!-- discovery methods -->
|
||||
<discovery-methods>
|
||||
<discovery-method>
|
||||
<service-type>mdns</service-type>
|
||||
<mdns-service-type>_hue._tcp.local.</mdns-service-type>
|
||||
</discovery-method>
|
||||
<discovery-method>
|
||||
<service-type>upnp</service-type>
|
||||
<match-properties>
|
||||
<match-property>
|
||||
<name>modelName</name>
|
||||
<regex>Philips hue bridge</regex>
|
||||
</match-property>
|
||||
</match-properties>
|
||||
</discovery-method>
|
||||
</discovery-methods>
|
||||
|
||||
</addon:addon>
|
||||
|
|
Loading…
Reference in New Issue