Skip to content

Commit

Permalink
Add a YAML file provider for semantic tags
Browse files Browse the repository at this point in the history
Files in folder conf/tags are loaded by this provider.

Related to #3619

Signed-off-by: Laurent Garnier <[email protected]>
  • Loading branch information
lolodomo committed Jun 28, 2023
1 parent 8eddad5 commit a6d93c1
Show file tree
Hide file tree
Showing 9 changed files with 586 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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.semantics.internal.config.yaml;

import java.util.List;
import java.util.Objects;

import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.semantics.model.yaml.YamlElement;
import org.openhab.core.semantics.model.yaml.YamlParseException;

/**
* 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 {

public String uid;
public String label;
public String description;
public List<String> synonyms;

public YamlSemanticTag() {
}

@Override
public String getId() {
return uid;
}

@Override
public void checkValidity() throws YamlParseException {
if (uid == null) {
throw new YamlParseException("uid missing");
}
}

@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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* 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.semantics.internal.config.yaml;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.common.registry.AbstractProvider;
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.openhab.core.semantics.model.yaml.YamlElement;
import org.openhab.core.semantics.model.yaml.YamlFile;
import org.openhab.core.semantics.model.yaml.YamlModelListener;
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 List<SemanticTag> tags = new ArrayList<>();

@Activate
public YamlSemanticTagProvider() {
}

@Deactivate
public void deactivate() {
tags.clear();
}

@Override
public Collection<SemanticTag> getAll() {
return tags.stream().sorted(Comparator.comparing(SemanticTag::getUID)).toList();
}

@Override
public String getRootName() {
return "tags";
}

@Override
public Class<? extends YamlFile> getFileClass() {
return YamlSemanticTags.class;
}

@Override
public Class<YamlSemanticTag> getElementClass() {
return YamlSemanticTag.class;
}

@Override
public void addedModel(String modelName, List<? 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, List<? 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, List<? 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* 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.semantics.internal.config.yaml;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.semantics.model.yaml.YamlElement;
import org.openhab.core.semantics.model.yaml.YamlFile;
import org.openhab.core.semantics.model.yaml.YamlParseException;

/**
* 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 YamlFile {

public List<YamlSemanticTag> tags = List.of();

@Override
public List<? extends YamlElement> getElements() {
return tags;
}

@Override
protected void checkVersion() throws YamlParseException {
if (version != 1) {
throw new YamlParseException("Version 1 required; please convert your file");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* 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.semantics.model.yaml;

/**
* The {@link YamlElement} interface offers an identifier to any element defined in a YAML configuration file.
*
* @author Laurent Garnier - Initial contribution
*/
public interface YamlElement {

String getId();

void checkValidity() throws YamlParseException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* 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.semantics.model.yaml;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* The {@link YamlFile} 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 YamlFile {

public int version;

public abstract List<? extends YamlElement> getElements();

protected abstract void checkVersion() throws YamlParseException;

public void checkValidity() throws YamlParseException {
checkVersion();
List<? extends YamlElement> elts = getElements();
long nbDistinctIds = elts.stream().map(YamlElement::getId).distinct().count();
if (nbDistinctIds < elts.size()) {
throw new YamlParseException((elts.size() - nbDistinctIds + 1) + " elements with same ids");
}
for (int i = 0; i < elts.size(); i++) {
try {
elts.get(i).checkValidity();
} catch (YamlParseException e) {
throw new YamlParseException("Error in element " + (i + 1) + ": " + e.getMessage());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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.semantics.model.yaml;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* The {@link YamlModelListener} interface is responsible for managing a particular model
* with data processed from YAML configuration files.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public interface YamlModelListener<T extends YamlElement> {

void addedModel(String modelName, List<? extends YamlElement> elements);

void updatedModel(String modelName, List<? extends YamlElement> elements);

void removedModel(String modelName, List<? extends YamlElement> elements);

String getRootName();

Class<? extends YamlFile> getFileClass();

Class<T> getElementClass();
}
Loading

0 comments on commit a6d93c1

Please sign in to comment.