mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
Add a YAML file provider for semantic tags (#3659)
* Add a YAML file provider for semantic tags Files in folder conf/tags are loaded by this provider. Related to #3619 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
152ffe3fbb
commit
070de55b27
@ -514,6 +514,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.model.yaml</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.ui</artifactId>
|
||||
|
29
bundles/org.openhab.core.model.yaml/.classpath
Normal file
29
bundles/org.openhab.core.model.yaml/.classpath
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="annotationpath" value="target/dependency"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
23
bundles/org.openhab.core.model.yaml/.project
Normal file
23
bundles/org.openhab.core.model.yaml/.project
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.core.model.yaml</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
14
bundles/org.openhab.core.model.yaml/NOTICE
Normal file
14
bundles/org.openhab.core.model.yaml/NOTICE
Normal file
@ -0,0 +1,14 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-core
|
||||
|
29
bundles/org.openhab.core.model.yaml/pom.xml
Normal file
29
bundles/org.openhab.core.model.yaml/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.reactor.bundles</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.core.model.yaml</artifactId>
|
||||
|
||||
<name>openHAB Core :: Bundles :: Model YAML</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.semantics</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.model.yaml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AbstractYamlFile} is the DTO base class used to map a YAML configuration file.
|
||||
*
|
||||
* A YAML configuration file consists of a version and a list of elements.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractYamlFile implements YamlFile {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractYamlFile.class);
|
||||
|
||||
/**
|
||||
* YAML file version
|
||||
*/
|
||||
public int version;
|
||||
|
||||
@Override
|
||||
public abstract List<? extends YamlElement> getElements();
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
// Checking duplicated elements
|
||||
List<? extends YamlElement> elts = getElements();
|
||||
long nbDistinctIds = elts.stream().map(YamlElement::getId).distinct().count();
|
||||
if (nbDistinctIds < elts.size()) {
|
||||
logger.debug("Elements with same ids detected in the file");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checking each element
|
||||
for (int i = 0; i < elts.size(); i++) {
|
||||
if (!elts.get(i).isValid()) {
|
||||
logger.debug("Error in element {}", i + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.model.yaml;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link YamlElement} interface offers an identifier and a check validity method
|
||||
* to any element defined in a YAML configuration file.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface YamlElement {
|
||||
|
||||
/**
|
||||
* Get the identifier of the YAML element
|
||||
*
|
||||
* @return the identifier as a string
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Check that the YAML element is valid
|
||||
*
|
||||
* @return true if all the checks are OK
|
||||
*/
|
||||
boolean isValid();
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.model.yaml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link YamlFile} is the interface to manage the generic content of a YAML configuration file.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface YamlFile {
|
||||
|
||||
/**
|
||||
* Get the list of elements present in the YAML file.
|
||||
*
|
||||
* @return the list of elements
|
||||
*/
|
||||
List<? extends YamlElement> getElements();
|
||||
|
||||
/**
|
||||
* Get the version present in the YAML file.
|
||||
*
|
||||
* @return the version in the file
|
||||
*/
|
||||
int getVersion();
|
||||
|
||||
/**
|
||||
* Check that the file content is valid.
|
||||
* It includes the check of duplicated elements (same identifier) and the check of each element.
|
||||
*
|
||||
* @return true if all the checks are OK
|
||||
*/
|
||||
boolean isValid();
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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.model.yaml;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link YamlModelListener} interface is responsible for managing a particular model type
|
||||
* with data processed from YAML configuration files.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface YamlModelListener<T extends YamlElement> {
|
||||
|
||||
/**
|
||||
* Method called by the model repository when elements from a model are added.
|
||||
*
|
||||
* @param modelName the name of the model
|
||||
* @param elements the collection of added elements
|
||||
*/
|
||||
void addedModel(String modelName, Collection<? extends YamlElement> elements);
|
||||
|
||||
/**
|
||||
* Method called by the model repository when elements from a model are updated.
|
||||
*
|
||||
* @param modelName the name of the model
|
||||
* @param elements the collection of updated elements
|
||||
*/
|
||||
void updatedModel(String modelName, Collection<? extends YamlElement> elements);
|
||||
|
||||
/**
|
||||
* Method called by the model repository when elements from a model are removed.
|
||||
*
|
||||
* @param modelName the name of the model
|
||||
* @param elements the collection of removed elements
|
||||
*/
|
||||
void removedModel(String modelName, Collection<? extends YamlElement> elements);
|
||||
|
||||
/**
|
||||
* Get the root name of this model type which is also the name of the root folder
|
||||
* containing the user files for this model type.
|
||||
*
|
||||
* A path is unexpected. What is expected is for example "items" or "things".
|
||||
*
|
||||
* @return the model root name
|
||||
*/
|
||||
String getRootName();
|
||||
|
||||
/**
|
||||
* Get the DTO class to be used for a file providing objects for this model type.
|
||||
*
|
||||
* @return the DTO file class
|
||||
*/
|
||||
Class<? extends AbstractYamlFile> getFileClass();
|
||||
|
||||
/**
|
||||
* Get the DTO class to be used for each object of this model type.
|
||||
*
|
||||
* @return the DTO element class
|
||||
*/
|
||||
Class<T> getElementClass();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.model.yaml;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link YamlParseException} is used when an error is detected when parsing the content
|
||||
* of a YAML configuration file.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class YamlParseException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public YamlParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public YamlParseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public YamlParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -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.model.yaml.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.model.yaml.AbstractYamlFile;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
import org.openhab.core.model.yaml.YamlModelListener;
|
||||
import org.openhab.core.model.yaml.YamlParseException;
|
||||
import org.openhab.core.service.WatchService;
|
||||
import org.openhab.core.service.WatchService.Kind;
|
||||
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;
|
||||
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
|
||||
/**
|
||||
* The {@link YamlModelRepository} is an OSGi service, that encapsulates all YAML file processing
|
||||
* including file monitoring to detect created, updated and removed YAML configuration files.
|
||||
* Data processed from these files are consumed by registered OSGi services that implement {@link YamlModelListener}.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class YamlModelRepository implements WatchService.WatchEventListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(YamlModelRepository.class);
|
||||
|
||||
private final WatchService watchService;
|
||||
private final Path watchPath;
|
||||
private final ObjectMapper yamlReader;
|
||||
|
||||
private final Map<String, List<YamlModelListener<?>>> listeners = new ConcurrentHashMap<>();
|
||||
private final Map<Path, List<? extends YamlElement>> objects = new ConcurrentHashMap<>();
|
||||
|
||||
@Activate
|
||||
public YamlModelRepository(@Reference(target = WatchService.CONFIG_WATCHER_FILTER) WatchService watchService) {
|
||||
this.watchService = watchService;
|
||||
this.yamlReader = new ObjectMapper(new YAMLFactory());
|
||||
yamlReader.findAndRegisterModules();
|
||||
|
||||
watchService.registerListener(this, Path.of(""));
|
||||
watchPath = watchService.getWatchPath();
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
watchService.unregisterListener(this);
|
||||
}
|
||||
|
||||
// The method is "synchronized" to avoid concurrent files processing
|
||||
@Override
|
||||
public synchronized void processWatchEvent(Kind kind, Path path) {
|
||||
Path fullPath = watchPath.resolve(path);
|
||||
String dirName = path.subpath(0, 1).toString();
|
||||
|
||||
if (Files.isDirectory(fullPath) || fullPath.toFile().isHidden() || !fullPath.toString().endsWith(".yaml")) {
|
||||
logger.trace("Ignored {}", fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
getListeners(dirName).forEach(listener -> processWatchEvent(dirName, kind, fullPath, listener));
|
||||
}
|
||||
|
||||
private void processWatchEvent(String dirName, Kind kind, Path fullPath, YamlModelListener<?> listener) {
|
||||
logger.debug("processWatchEvent dirName={} kind={} fullPath={} listener={}", dirName, kind, fullPath,
|
||||
listener.getClass().getSimpleName());
|
||||
Map<String, ? extends YamlElement> oldObjects;
|
||||
Map<String, ? extends YamlElement> newObjects;
|
||||
if (kind == WatchService.Kind.DELETE) {
|
||||
newObjects = Map.of();
|
||||
|
||||
List<? extends YamlElement> oldListObjects = objects.remove(fullPath);
|
||||
if (oldListObjects == null) {
|
||||
oldListObjects = List.of();
|
||||
}
|
||||
oldObjects = oldListObjects.stream().collect(Collectors.toMap(YamlElement::getId, obj -> obj));
|
||||
} else {
|
||||
AbstractYamlFile yamlData;
|
||||
try {
|
||||
yamlData = readYamlFile(fullPath, listener.getFileClass());
|
||||
} catch (YamlParseException e) {
|
||||
logger.warn("Failed to parse Yaml file {} with DTO class {}: {}", fullPath,
|
||||
listener.getFileClass().getName(), e.getMessage());
|
||||
return;
|
||||
}
|
||||
List<? extends YamlElement> newListObjects = yamlData.getElements();
|
||||
newObjects = newListObjects.stream().collect(Collectors.toMap(YamlElement::getId, obj -> obj));
|
||||
|
||||
List<? extends YamlElement> oldListObjects = objects.get(fullPath);
|
||||
if (oldListObjects == null) {
|
||||
oldListObjects = List.of();
|
||||
}
|
||||
oldObjects = oldListObjects.stream().collect(Collectors.toMap(YamlElement::getId, obj -> obj));
|
||||
|
||||
objects.put(fullPath, newListObjects);
|
||||
}
|
||||
|
||||
String modelName = fullPath.toFile().getName();
|
||||
modelName = modelName.substring(0, modelName.indexOf(".yaml"));
|
||||
List<? extends YamlElement> listElements;
|
||||
listElements = oldObjects.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getClass().equals(listener.getElementClass())
|
||||
&& !newObjects.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue).toList();
|
||||
if (!listElements.isEmpty()) {
|
||||
listener.removedModel(modelName, listElements);
|
||||
}
|
||||
|
||||
listElements = newObjects.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getClass().equals(listener.getElementClass())
|
||||
&& !oldObjects.containsKey(entry.getKey()))
|
||||
.map(Map.Entry::getValue).toList();
|
||||
if (!listElements.isEmpty()) {
|
||||
listener.addedModel(modelName, listElements);
|
||||
}
|
||||
|
||||
// Object is ignored if unchanged
|
||||
listElements = newObjects.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getClass().equals(listener.getElementClass())
|
||||
&& oldObjects.containsKey(entry.getKey())
|
||||
&& !entry.getValue().equals(oldObjects.get(entry.getKey())))
|
||||
.map(Map.Entry::getValue).toList();
|
||||
if (!listElements.isEmpty()) {
|
||||
listener.updatedModel(modelName, listElements);
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractYamlFile readYamlFile(Path path, Class<? extends AbstractYamlFile> dtoClass)
|
||||
throws YamlParseException {
|
||||
logger.debug("readYamlFile {} with {}", path.toFile().getAbsolutePath(), dtoClass.getName());
|
||||
try {
|
||||
AbstractYamlFile dto = yamlReader.readValue(path.toFile(), dtoClass);
|
||||
if (!dto.isValid()) {
|
||||
throw new YamlParseException("The file is not valid, some checks failed!");
|
||||
}
|
||||
return dto;
|
||||
} catch (IOException e) {
|
||||
throw new YamlParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addYamlModelListener(YamlModelListener<?> listener) {
|
||||
String dirName = listener.getRootName();
|
||||
logger.debug("Adding model listener for {}", dirName);
|
||||
getListeners(dirName).add(listener);
|
||||
|
||||
// Load all existing YAML files
|
||||
try (Stream<Path> stream = Files.walk(watchPath.resolve(dirName))) {
|
||||
stream.forEach(path -> {
|
||||
if (!Files.isDirectory(path) && !path.toFile().isHidden() && path.toString().endsWith(".yaml")) {
|
||||
processWatchEvent(dirName, Kind.CREATE, path, listener);
|
||||
}
|
||||
});
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeYamlModelListener(YamlModelListener<?> listener) {
|
||||
String dirName = listener.getRootName();
|
||||
logger.debug("Removing model listener for {}", dirName);
|
||||
getListeners(dirName).remove(listener);
|
||||
}
|
||||
|
||||
private List<YamlModelListener<?>> getListeners(String dirName) {
|
||||
return Objects.requireNonNull(listeners.computeIfAbsent(dirName, k -> new CopyOnWriteArrayList<>()));
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.model.yaml.internal.semantics;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link YamlSemanticTag} is a data transfer object used to serialize a semantic tag
|
||||
* in a YAML configuration file.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
public class YamlSemanticTag implements YamlElement {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(YamlSemanticTag.class);
|
||||
|
||||
public String uid;
|
||||
public String label;
|
||||
public String description;
|
||||
public List<String> synonyms;
|
||||
|
||||
public YamlSemanticTag() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
if (uid == null) {
|
||||
logger.debug("uid missing");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
} else if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
YamlSemanticTag that = (YamlSemanticTag) obj;
|
||||
return Objects.equals(uid, that.uid) && Objects.equals(label, that.label)
|
||||
&& Objects.equals(description, that.description) && Objects.equals(synonyms, that.synonyms);
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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.model.yaml.internal.semantics;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.registry.AbstractProvider;
|
||||
import org.openhab.core.model.yaml.AbstractYamlFile;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
import org.openhab.core.model.yaml.YamlModelListener;
|
||||
import org.openhab.core.semantics.SemanticTag;
|
||||
import org.openhab.core.semantics.SemanticTagImpl;
|
||||
import org.openhab.core.semantics.SemanticTagProvider;
|
||||
import org.openhab.core.semantics.SemanticTagRegistry;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link YamlSemanticTagProvider} is an OSGi service, that allows to define semantic tags
|
||||
* in YAML configuration files in folder conf/tags.
|
||||
* Files can be added, updated or removed at runtime.
|
||||
* These semantic tags are automatically exposed to the {@link SemanticTagRegistry}.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { SemanticTagProvider.class, YamlSemanticTagProvider.class,
|
||||
YamlModelListener.class })
|
||||
public class YamlSemanticTagProvider extends AbstractProvider<SemanticTag>
|
||||
implements SemanticTagProvider, YamlModelListener<YamlSemanticTag> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(YamlSemanticTagProvider.class);
|
||||
|
||||
private final Set<SemanticTag> tags = new TreeSet<>(Comparator.comparing(SemanticTag::getUID));
|
||||
|
||||
@Activate
|
||||
public YamlSemanticTagProvider() {
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
tags.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SemanticTag> getAll() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootName() {
|
||||
return "tags";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends AbstractYamlFile> getFileClass() {
|
||||
return YamlSemanticTags.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<YamlSemanticTag> getElementClass() {
|
||||
return YamlSemanticTag.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addedModel(String modelName, Collection<? extends YamlElement> elements) {
|
||||
List<SemanticTag> added = elements.stream().map(e -> mapSemanticTag((YamlSemanticTag) e))
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID)).toList();
|
||||
tags.addAll(added);
|
||||
added.forEach(t -> {
|
||||
logger.debug("model {} added tag {}", modelName, t.getUID());
|
||||
notifyListenersAboutAddedElement(t);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatedModel(String modelName, Collection<? extends YamlElement> elements) {
|
||||
List<SemanticTag> updated = elements.stream().map(e -> mapSemanticTag((YamlSemanticTag) e)).toList();
|
||||
updated.forEach(t -> {
|
||||
tags.stream().filter(tag -> tag.getUID().equals(t.getUID())).findFirst().ifPresentOrElse(oldTag -> {
|
||||
tags.remove(oldTag);
|
||||
tags.add(t);
|
||||
logger.debug("model {} updated tag {}", modelName, t.getUID());
|
||||
notifyListenersAboutUpdatedElement(oldTag, t);
|
||||
}, () -> logger.debug("model {} tag {} not found", modelName, t.getUID()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removedModel(String modelName, Collection<? extends YamlElement> elements) {
|
||||
List<SemanticTag> removed = elements.stream().map(e -> mapSemanticTag((YamlSemanticTag) e))
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID).reversed()).toList();
|
||||
removed.forEach(t -> {
|
||||
tags.stream().filter(tag -> tag.getUID().equals(t.getUID())).findFirst().ifPresentOrElse(oldTag -> {
|
||||
tags.remove(oldTag);
|
||||
logger.debug("model {} removed tag {}", modelName, t.getUID());
|
||||
notifyListenersAboutRemovedElement(oldTag);
|
||||
}, () -> logger.debug("model {} tag {} not found", modelName, t.getUID()));
|
||||
});
|
||||
}
|
||||
|
||||
private SemanticTag mapSemanticTag(YamlSemanticTag tagDTO) {
|
||||
if (tagDTO.uid == null) {
|
||||
throw new IllegalArgumentException("The argument 'tagDTO.uid' must not be null.");
|
||||
}
|
||||
return new SemanticTagImpl(tagDTO.uid, tagDTO.label, tagDTO.description, tagDTO.synonyms);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.model.yaml.internal.semantics;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.model.yaml.AbstractYamlFile;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
|
||||
/**
|
||||
* The {@link YamlSemanticTags} is a data transfer object used to serialize a list of semantic tags
|
||||
* in a YAML configuration file.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class YamlSemanticTags extends AbstractYamlFile {
|
||||
|
||||
public List<YamlSemanticTag> tags = List.of();
|
||||
|
||||
@Override
|
||||
public List<? extends YamlElement> getElements() {
|
||||
return tags;
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.registry.AbstractProvider;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@ -33,6 +34,7 @@ import org.openhab.core.semantics.Equipment;
|
||||
import org.openhab.core.semantics.Location;
|
||||
import org.openhab.core.semantics.Point;
|
||||
import org.openhab.core.semantics.Property;
|
||||
import org.openhab.core.semantics.SemanticTag;
|
||||
import org.openhab.core.semantics.SemanticTagRegistry;
|
||||
import org.openhab.core.semantics.SemanticTags;
|
||||
import org.openhab.core.semantics.Tag;
|
||||
@ -72,11 +74,16 @@ public class SemanticsMetadataProvider extends AbstractProvider<Metadata>
|
||||
private final Map<String, Metadata> semantics = new TreeMap<>(String::compareTo);
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
|
||||
private SemanticTagRegistryChangeListener listener;
|
||||
|
||||
@Activate
|
||||
public SemanticsMetadataProvider(final @Reference ItemRegistry itemRegistry,
|
||||
final @Reference SemanticTagRegistry semanticTagRegistry) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.semanticTagRegistry = semanticTagRegistry;
|
||||
this.listener = new SemanticTagRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
@Activate
|
||||
@ -86,10 +93,12 @@ public class SemanticsMetadataProvider extends AbstractProvider<Metadata>
|
||||
processItem(item);
|
||||
}
|
||||
itemRegistry.addRegistryChangeListener(this);
|
||||
semanticTagRegistry.addRegistryChangeListener(listener);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
semanticTagRegistry.removeRegistryChangeListener(listener);
|
||||
itemRegistry.removeRegistryChangeListener(this);
|
||||
semantics.clear();
|
||||
}
|
||||
@ -280,4 +289,28 @@ public class SemanticsMetadataProvider extends AbstractProvider<Metadata>
|
||||
public void updated(Item oldItem, Item item) {
|
||||
processItem(item);
|
||||
}
|
||||
|
||||
private class SemanticTagRegistryChangeListener implements RegistryChangeListener<SemanticTag> {
|
||||
|
||||
private SemanticsMetadataProvider provider;
|
||||
|
||||
public SemanticTagRegistryChangeListener(SemanticsMetadataProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(SemanticTag element) {
|
||||
provider.allItemsChanged(List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(SemanticTag element) {
|
||||
provider.allItemsChanged(List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(SemanticTag oldElement, SemanticTag element) {
|
||||
provider.allItemsChanged(List.of());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@
|
||||
<module>org.openhab.core.model.thing</module>
|
||||
<module>org.openhab.core.model.thing.ide</module>
|
||||
<module>org.openhab.core.model.thing.runtime</module>
|
||||
<module>org.openhab.core.model.yaml</module>
|
||||
<module>org.openhab.core.storage.json</module>
|
||||
<module>org.openhab.core.test</module>
|
||||
<module>org.openhab.core.test.magic</module>
|
||||
|
@ -390,6 +390,13 @@
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.model.lsp/${project.version}</bundle>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-model-yaml" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.model.yaml/${project.version}</bundle>
|
||||
<requirement>openhab.tp;filter:="(feature=jackson)"</requirement>
|
||||
<feature dependency="true">openhab.tp-jackson</feature>
|
||||
</feature>
|
||||
|
||||
<feature name="openhab-core-storage-json" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
|
||||
@ -434,6 +441,7 @@
|
||||
<feature>openhab-core-model-script</feature>
|
||||
<feature>openhab-core-model-sitemap</feature>
|
||||
<feature>openhab-core-model-thing</feature>
|
||||
<feature>openhab-core-model-yaml</feature>
|
||||
<feature>openhab-core-ui-icon</feature>
|
||||
<feature>openhab-core-storage-json</feature>
|
||||
<feature>openhab-runtime-certificate</feature>
|
||||
|
Loading…
Reference in New Issue
Block a user