Rule Template installation fixes (#4591)
* Fix file based rule templates * Add YAML Template parser * Refactor marketplace rule template parsing * Prevent file system access for WatchService DELETE events Trying to check if deleted files are hidden, are readable or are directories will result in IOExceptions on many file systems, so that no action will be taken for deletions. Signed-off-by: Arne Seime <arne.seime@gmail.com>pull/4561/head
parent
3018a8b0f1
commit
fe9af132aa
|
@ -19,14 +19,20 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.automation.Module;
|
||||||
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
||||||
import org.openhab.core.automation.dto.RuleTemplateDTOMapper;
|
import org.openhab.core.automation.dto.RuleTemplateDTOMapper;
|
||||||
import org.openhab.core.automation.parser.Parser;
|
import org.openhab.core.automation.parser.Parser;
|
||||||
import org.openhab.core.automation.parser.ParsingException;
|
import org.openhab.core.automation.parser.ParsingException;
|
||||||
|
import org.openhab.core.automation.parser.ParsingNestedException;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException.ObjectType;
|
||||||
import org.openhab.core.automation.template.RuleTemplate;
|
import org.openhab.core.automation.template.RuleTemplate;
|
||||||
import org.openhab.core.automation.template.RuleTemplateProvider;
|
import org.openhab.core.automation.template.RuleTemplateProvider;
|
||||||
import org.openhab.core.common.registry.AbstractManagedProvider;
|
import org.openhab.core.common.registry.AbstractManagedProvider;
|
||||||
|
@ -34,8 +40,8 @@ import org.openhab.core.storage.StorageService;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
import org.osgi.service.component.annotations.Reference;
|
import org.osgi.service.component.annotations.Reference;
|
||||||
import org.slf4j.Logger;
|
import org.osgi.service.component.annotations.ReferenceCardinality;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.osgi.service.component.annotations.ReferencePolicy;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
@ -46,27 +52,48 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
*
|
*
|
||||||
* @author Kai Kreuzer - Initial contribution and API
|
* @author Kai Kreuzer - Initial contribution and API
|
||||||
* @author Yannick Schaus - refactoring
|
* @author Yannick Schaus - refactoring
|
||||||
*
|
* @author Arne Seime - refactored rule template parsing
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(service = { MarketplaceRuleTemplateProvider.class, RuleTemplateProvider.class })
|
@Component(service = { MarketplaceRuleTemplateProvider.class, RuleTemplateProvider.class })
|
||||||
public class MarketplaceRuleTemplateProvider extends AbstractManagedProvider<RuleTemplate, String, RuleTemplateDTO>
|
public class MarketplaceRuleTemplateProvider extends AbstractManagedProvider<RuleTemplate, String, RuleTemplateDTO>
|
||||||
implements RuleTemplateProvider {
|
implements RuleTemplateProvider {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(MarketplaceRuleTemplateProvider.class);
|
private final Map<String, Parser<RuleTemplate>> parsers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Parser<RuleTemplate> parser;
|
|
||||||
ObjectMapper yamlMapper;
|
ObjectMapper yamlMapper;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public MarketplaceRuleTemplateProvider(final @Reference StorageService storageService,
|
public MarketplaceRuleTemplateProvider(final @Reference StorageService storageService) {
|
||||||
final @Reference(target = "(&(format=json)(parser.type=parser.template))") Parser<RuleTemplate> parser) {
|
|
||||||
super(storageService);
|
super(storageService);
|
||||||
this.parser = parser;
|
|
||||||
this.yamlMapper = new ObjectMapper(new YAMLFactory());
|
this.yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||||
yamlMapper.findAndRegisterModules();
|
yamlMapper.findAndRegisterModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a {@link Parser}.
|
||||||
|
*
|
||||||
|
* @param parser the {@link Parser} service to register.
|
||||||
|
* @param properties the properties.
|
||||||
|
*/
|
||||||
|
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policy = ReferencePolicy.DYNAMIC, target = "(parser.type=parser.template)")
|
||||||
|
public void addParser(Parser<RuleTemplate> parser, Map<String, String> properties) {
|
||||||
|
String parserType = properties.get(Parser.FORMAT);
|
||||||
|
parserType = parserType == null ? Parser.FORMAT_JSON : parserType;
|
||||||
|
parsers.put(parserType, parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a {@link Parser}.
|
||||||
|
*
|
||||||
|
* @param parser the {@link Parser} service to unregister.
|
||||||
|
* @param properties the properties.
|
||||||
|
*/
|
||||||
|
public void removeParser(Parser<RuleTemplate> parser, Map<String, String> properties) {
|
||||||
|
String parserType = properties.get(Parser.FORMAT);
|
||||||
|
parserType = parserType == null ? Parser.FORMAT_JSON : parserType;
|
||||||
|
parsers.remove(parserType);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable RuleTemplate getTemplate(String uid, @Nullable Locale locale) {
|
public @Nullable RuleTemplate getTemplate(String uid, @Nullable Locale locale) {
|
||||||
return get(uid);
|
return get(uid);
|
||||||
|
@ -98,55 +125,83 @@ public class MarketplaceRuleTemplateProvider extends AbstractManagedProvider<Rul
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This adds a new rule template to the persistent storage from its JSON representation.
|
* Adds a new rule template to persistent storage from its {@code JSON} representation.
|
||||||
*
|
*
|
||||||
* @param uid the UID to be used for the template
|
* @param uid the marketplace UID to use.
|
||||||
* @param json the template content as a JSON string
|
* @param json the template content as a {@code JSON} string
|
||||||
*
|
* @throws ParsingException If the parsing fails.
|
||||||
* @throws ParsingException if the content cannot be parsed correctly
|
* @throws ValidationException If the validation fails.
|
||||||
*/
|
*/
|
||||||
public void addTemplateAsJSON(String uid, String json) throws ParsingException {
|
public void addTemplateAsJSON(String uid, String json) throws ParsingException, ValidationException {
|
||||||
|
addTemplate(uid, json, Parser.FORMAT_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new rule template to persistent storage from its {@code YAML} representation.
|
||||||
|
*
|
||||||
|
* @param uid the marketplace UID to use.
|
||||||
|
* @param yaml the template content as a {@code YAML} string
|
||||||
|
* @throws ParsingException If the parsing fails.
|
||||||
|
* @throws ValidationException If the validation fails.
|
||||||
|
*/
|
||||||
|
public void addTemplateAsYAML(String uid, String yaml) throws ParsingException, ValidationException {
|
||||||
|
addTemplate(uid, yaml, Parser.FORMAT_YAML);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds one or ore new {@link RuleTemplate}s parsed from the provided content using the specified parser.
|
||||||
|
*
|
||||||
|
* @param uid the marketplace UID to use.
|
||||||
|
* @param content the content to parse.
|
||||||
|
* @param format the format to parse.
|
||||||
|
* @throws ParsingException If the parsing fails.
|
||||||
|
* @throws ValidationException If the validation fails.
|
||||||
|
*/
|
||||||
|
protected void addTemplate(String uid, String content, String format) throws ParsingException, ValidationException {
|
||||||
|
Parser<RuleTemplate> parser = parsers.get(format);
|
||||||
|
|
||||||
|
// The parser might not have been registered yet
|
||||||
|
if (parser == null) {
|
||||||
|
throw new ParsingException(new ParsingNestedException(ParsingNestedException.TEMPLATE,
|
||||||
|
"No " + format.toUpperCase(Locale.ROOT) + " parser available", null));
|
||||||
|
}
|
||||||
|
|
||||||
try (InputStreamReader isr = new InputStreamReader(
|
try (InputStreamReader isr = new InputStreamReader(
|
||||||
new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)))) {
|
new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))) {
|
||||||
Set<RuleTemplate> templates = parser.parse(isr);
|
Set<RuleTemplate> templates = parser.parse(isr);
|
||||||
if (templates.size() != 1) {
|
|
||||||
throw new IllegalArgumentException("JSON must contain exactly one template!");
|
// Add a tag with the marketplace add-on ID to be able to identify the template in the registry
|
||||||
} else {
|
Set<String> tags;
|
||||||
RuleTemplate entry = templates.iterator().next();
|
for (RuleTemplate template : templates) {
|
||||||
// add a tag with the add-on ID to be able to identify the widget in the registry
|
validateTemplate(template);
|
||||||
entry.getTags().add(uid);
|
tags = new HashSet<String>(template.getTags());
|
||||||
RuleTemplate template = new RuleTemplate(entry.getUID(), entry.getLabel(), entry.getDescription(),
|
tags.add(uid);
|
||||||
entry.getTags(), entry.getTriggers(), entry.getConditions(), entry.getActions(),
|
add(new RuleTemplate(template.getUID(), template.getLabel(), template.getDescription(), tags,
|
||||||
entry.getConfigurationDescriptions(), entry.getVisibility());
|
template.getTriggers(), template.getConditions(), template.getActions(),
|
||||||
add(template);
|
template.getConfigurationDescriptions(), template.getVisibility()));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Cannot close input stream.", e);
|
// Impossible for ByteArrayInputStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This adds a new rule template to the persistent storage from its YAML representation.
|
* Validates that the parsed template is valid.
|
||||||
*
|
*
|
||||||
* @param uid the UID to be used for the template
|
* @param template the {@link RuleTemplate} to validate.
|
||||||
* @param yaml the template content as a YAML string
|
* @throws ValidationException If the validation failed.
|
||||||
*
|
|
||||||
* @throws ParsingException if the content cannot be parsed correctly
|
|
||||||
*/
|
*/
|
||||||
public void addTemplateAsYAML(String uid, String yaml) throws ParsingException {
|
@SuppressWarnings("null")
|
||||||
try {
|
protected void validateTemplate(RuleTemplate template) throws ValidationException {
|
||||||
RuleTemplateDTO dto = yamlMapper.readValue(yaml, RuleTemplateDTO.class);
|
String s;
|
||||||
// add a tag with the add-on ID to be able to identify the widget in the registry
|
if ((s = template.getUID()) == null || s.isBlank()) {
|
||||||
dto.tags = new HashSet<@Nullable String>((dto.tags != null) ? dto.tags : new HashSet<>());
|
throw new ValidationException(ObjectType.TEMPLATE, null, "UID cannot be blank");
|
||||||
dto.tags.add(uid);
|
}
|
||||||
RuleTemplate entry = RuleTemplateDTOMapper.map(dto);
|
if ((s = template.getLabel()) == null || s.isBlank()) {
|
||||||
RuleTemplate template = new RuleTemplate(entry.getUID(), entry.getLabel(), entry.getDescription(),
|
throw new ValidationException(ObjectType.TEMPLATE, template.getUID(), "Label cannot be blank");
|
||||||
entry.getTags(), entry.getTriggers(), entry.getConditions(), entry.getActions(),
|
}
|
||||||
entry.getConfigurationDescriptions(), entry.getVisibility());
|
if (template.getModules(Module.class).isEmpty()) {
|
||||||
add(template);
|
throw new ValidationException(ObjectType.TEMPLATE, template.getUID(), "There must be at least one module");
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Unable to parse YAML: {}", e.getMessage());
|
|
||||||
throw new IllegalArgumentException("Unable to parse YAML");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,10 +85,10 @@ public class CommunityRuleTemplateAddonHandler implements MarketplaceAddonHandle
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Rule template from marketplace cannot be downloaded: {}", e.getMessage());
|
logger.error("Rule template from marketplace cannot be downloaded: {}", e.getMessage());
|
||||||
throw new MarketplaceHandlerException("Template cannot be downloaded.", e);
|
throw new MarketplaceHandlerException("Rule template cannot be downloaded", e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Rule template from marketplace is invalid: {}", e.getMessage());
|
logger.error("Failed to add rule template from the marketplace: {}", e.getMessage());
|
||||||
throw new MarketplaceHandlerException("Template is not valid.", e);
|
throw new MarketplaceHandlerException("Rule template is invalid", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ public abstract class AbstractScriptDependencyTracker
|
||||||
@Override
|
@Override
|
||||||
public void processWatchEvent(WatchService.Kind kind, Path path) {
|
public void processWatchEvent(WatchService.Kind kind, Path path) {
|
||||||
File file = libraryPath.resolve(path).toFile();
|
File file = libraryPath.resolve(path).toFile();
|
||||||
if (!file.isHidden() && (kind == DELETE || (file.canRead() && (kind == CREATE || kind == MODIFY)))) {
|
if (kind == DELETE || (!file.isHidden() && file.canRead() && (kind == CREATE || kind == MODIFY))) {
|
||||||
dependencyChanged(file.toString());
|
dependencyChanged(file.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 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.automation.internal.parser.jackson;
|
||||||
|
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.automation.parser.Parser;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class that can be used by YAML parsers for the different entity types.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the entities to parse
|
||||||
|
*
|
||||||
|
* @author Arne Seime - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class AbstractJacksonYAMLParser<T> implements Parser<T> {
|
||||||
|
|
||||||
|
/** The YAML object mapper instance */
|
||||||
|
protected static final ObjectMapper yamlMapper;
|
||||||
|
|
||||||
|
static {
|
||||||
|
yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||||
|
yamlMapper.findAndRegisterModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Set<T> dataObjects, OutputStreamWriter writer) throws Exception {
|
||||||
|
for (T dataObject : dataObjects) {
|
||||||
|
yamlMapper.writeValue(writer, dataObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 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.automation.internal.parser.jackson;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
||||||
|
import org.openhab.core.automation.dto.RuleTemplateDTOMapper;
|
||||||
|
import org.openhab.core.automation.parser.Parser;
|
||||||
|
import org.openhab.core.automation.parser.ParsingException;
|
||||||
|
import org.openhab.core.automation.parser.ParsingNestedException;
|
||||||
|
import org.openhab.core.automation.template.Template;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can parse and serialize sets of {@link Template}s.
|
||||||
|
*
|
||||||
|
* @author Arne Seime - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(immediate = true, service = Parser.class, property = { "parser.type=parser.template", "format=yaml" })
|
||||||
|
public class TemplateYAMLParser extends AbstractJacksonYAMLParser<Template> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Template> parse(InputStreamReader reader) throws ParsingException {
|
||||||
|
try {
|
||||||
|
Set<Template> templates = new HashSet<>();
|
||||||
|
JsonNode rootNode = yamlMapper.readTree(reader);
|
||||||
|
if (rootNode.isArray()) {
|
||||||
|
List<RuleTemplateDTO> templateDtos = yamlMapper.convertValue(rootNode,
|
||||||
|
new TypeReference<List<RuleTemplateDTO>>() {
|
||||||
|
});
|
||||||
|
for (RuleTemplateDTO templateDTO : templateDtos) {
|
||||||
|
templates.add(RuleTemplateDTOMapper.map(templateDTO));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RuleTemplateDTO templateDto = yamlMapper.convertValue(rootNode, new TypeReference<RuleTemplateDTO>() {
|
||||||
|
});
|
||||||
|
templates.add(RuleTemplateDTOMapper.map(templateDto));
|
||||||
|
}
|
||||||
|
return templates;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ParsingException(new ParsingNestedException(ParsingNestedException.TEMPLATE, null, e));
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.OpenHAB;
|
import org.openhab.core.OpenHAB;
|
||||||
import org.openhab.core.automation.parser.Parser;
|
import org.openhab.core.automation.parser.Parser;
|
||||||
import org.openhab.core.automation.parser.ParsingException;
|
import org.openhab.core.automation.parser.ParsingException;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException;
|
||||||
import org.openhab.core.automation.template.Template;
|
import org.openhab.core.automation.template.Template;
|
||||||
import org.openhab.core.automation.template.TemplateProvider;
|
import org.openhab.core.automation.template.TemplateProvider;
|
||||||
import org.openhab.core.automation.type.ModuleType;
|
import org.openhab.core.automation.type.ModuleType;
|
||||||
|
@ -50,6 +51,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* {@link ProviderChangeListener}s for adding, updating and removing the {@link ModuleType}s or {@link Template}s.
|
* {@link ProviderChangeListener}s for adding, updating and removing the {@link ModuleType}s or {@link Template}s.
|
||||||
*
|
*
|
||||||
* @author Ana Dimova - Initial contribution
|
* @author Ana Dimova - Initial contribution
|
||||||
|
* @author Arne Seime - Added object validation support
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class AbstractFileProvider<@NonNull E> implements Provider<E> {
|
public abstract class AbstractFileProvider<@NonNull E> implements Provider<E> {
|
||||||
|
@ -260,19 +262,42 @@ public abstract class AbstractFileProvider<@NonNull E> implements Provider<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("null")
|
||||||
protected void updateProvidedObjectsHolder(URL url, Set<E> providedObjects) {
|
protected void updateProvidedObjectsHolder(URL url, Set<E> providedObjects) {
|
||||||
if (providedObjects != null && !providedObjects.isEmpty()) {
|
if (providedObjects != null && !providedObjects.isEmpty()) {
|
||||||
List<String> uids = new ArrayList<>();
|
List<String> uids = new ArrayList<>();
|
||||||
for (E providedObject : providedObjects) {
|
for (E providedObject : providedObjects) {
|
||||||
|
try {
|
||||||
|
validateObject(providedObject);
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
logger.warn("Rejecting \"{}\" because the validation failed: {}", url, e.getMessage());
|
||||||
|
logger.trace("", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String uid = getUID(providedObject);
|
String uid = getUID(providedObject);
|
||||||
|
if (providerPortfolio.entrySet().stream().filter(e -> !url.equals(e.getKey()))
|
||||||
|
.flatMap(e -> e.getValue().stream()).anyMatch(u -> uid.equals(u))) {
|
||||||
|
logger.warn("Rejecting \"{}\" from \"{}\" because the UID is already registered", uid, url);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
uids.add(uid);
|
uids.add(uid);
|
||||||
final @Nullable E oldProvidedObject = providedObjectsHolder.put(uid, providedObject);
|
final @Nullable E oldProvidedObject = providedObjectsHolder.put(uid, providedObject);
|
||||||
notifyListeners(oldProvidedObject, providedObject);
|
notifyListeners(oldProvidedObject, providedObject);
|
||||||
}
|
}
|
||||||
providerPortfolio.put(url, uids);
|
if (!uids.isEmpty()) {
|
||||||
|
providerPortfolio.put(url, uids);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the parsed object is valid. For no validation, create an empty method.
|
||||||
|
*
|
||||||
|
* @param object the object to validate.
|
||||||
|
* @throws ValidationException If the validation failed.
|
||||||
|
*/
|
||||||
|
protected abstract void validateObject(E object) throws ValidationException;
|
||||||
|
|
||||||
protected void removeElements(@Nullable List<String> objectsForRemove) {
|
protected void removeElements(@Nullable List<String> objectsForRemove) {
|
||||||
if (objectsForRemove != null) {
|
if (objectsForRemove != null) {
|
||||||
for (String removedObject : objectsForRemove) {
|
for (String removedObject : objectsForRemove) {
|
||||||
|
|
|
@ -12,11 +12,14 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.core.automation.internal.provider.file;
|
package org.openhab.core.automation.internal.provider.file;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.service.WatchService;
|
import org.openhab.core.service.WatchService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an implementation of {@link WatchService.WatchEventListener} which is responsible for tracking file
|
* This class is an implementation of {@link WatchService.WatchEventListener} which is responsible for tracking file
|
||||||
|
@ -25,6 +28,7 @@ import org.openhab.core.service.WatchService;
|
||||||
* It provides functionality for tracking {@link #watchingDir} changes to import or remove the automation objects.
|
* It provides functionality for tracking {@link #watchingDir} changes to import or remove the automation objects.
|
||||||
*
|
*
|
||||||
* @author Ana Dimova - Initial contribution
|
* @author Ana Dimova - Initial contribution
|
||||||
|
* @author Arne Seime - Fixed watch event handling
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
|
@ -33,6 +37,7 @@ public class AutomationWatchService implements WatchService.WatchEventListener {
|
||||||
private final WatchService watchService;
|
private final WatchService watchService;
|
||||||
private final Path watchingDir;
|
private final Path watchingDir;
|
||||||
private AbstractFileProvider provider;
|
private AbstractFileProvider provider;
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(AutomationWatchService.class);
|
||||||
|
|
||||||
public AutomationWatchService(AbstractFileProvider provider, WatchService watchService, String watchingDir) {
|
public AutomationWatchService(AbstractFileProvider provider, WatchService watchService, String watchingDir) {
|
||||||
this.watchService = watchService;
|
this.watchService = watchService;
|
||||||
|
@ -54,13 +59,17 @@ public class AutomationWatchService implements WatchService.WatchEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processWatchEvent(WatchService.Kind kind, Path path) {
|
public void processWatchEvent(WatchService.Kind kind, Path path) {
|
||||||
File file = path.toFile();
|
Path fullPath = watchingDir.resolve(path);
|
||||||
if (!file.isHidden()) {
|
try {
|
||||||
if (kind == WatchService.Kind.DELETE) {
|
if (kind == WatchService.Kind.DELETE) {
|
||||||
provider.removeResources(file);
|
provider.removeResources(fullPath.toFile());
|
||||||
} else if (file.canRead()) {
|
} else if (!Files.isHidden(fullPath)
|
||||||
provider.importResources(file);
|
&& (kind == WatchService.Kind.CREATE || kind == WatchService.Kind.MODIFY)) {
|
||||||
|
provider.importResources(fullPath.toFile());
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to process automation watch event {} for \"{}\": {}", kind, fullPath, e.getMessage());
|
||||||
|
logger.trace("", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.automation.parser.Parser;
|
import org.openhab.core.automation.parser.Parser;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException.ObjectType;
|
||||||
import org.openhab.core.automation.type.ModuleType;
|
import org.openhab.core.automation.type.ModuleType;
|
||||||
import org.openhab.core.automation.type.ModuleTypeProvider;
|
import org.openhab.core.automation.type.ModuleTypeProvider;
|
||||||
import org.openhab.core.service.WatchService;
|
import org.openhab.core.service.WatchService;
|
||||||
|
@ -29,6 +31,7 @@ import org.osgi.service.component.annotations.ReferencePolicy;
|
||||||
* This class is a wrapper of {@link ModuleTypeProvider}, responsible for initializing the WatchService.
|
* This class is a wrapper of {@link ModuleTypeProvider}, responsible for initializing the WatchService.
|
||||||
*
|
*
|
||||||
* @author Ana Dimova - Initial contribution
|
* @author Ana Dimova - Initial contribution
|
||||||
|
* @author Arne Seime - Added module validation support
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(immediate = true, service = ModuleTypeProvider.class)
|
@Component(immediate = true, service = ModuleTypeProvider.class)
|
||||||
|
@ -62,4 +65,16 @@ public class ModuleTypeFileProviderWatcher extends ModuleTypeFileProvider {
|
||||||
public void removeParser(Parser<ModuleType> parser, Map<String, String> properties) {
|
public void removeParser(Parser<ModuleType> parser, Map<String, String> properties) {
|
||||||
super.removeParser(parser, properties);
|
super.removeParser(parser, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
@Override
|
||||||
|
protected void validateObject(ModuleType moduleType) throws ValidationException {
|
||||||
|
String s;
|
||||||
|
if ((s = moduleType.getUID()) == null || s.isBlank()) {
|
||||||
|
throw new ValidationException(ObjectType.MODULE_TYPE, null, "UID cannot be blank");
|
||||||
|
}
|
||||||
|
if ((s = moduleType.getLabel()) == null || s.isBlank()) {
|
||||||
|
throw new ValidationException(ObjectType.MODULE_TYPE, moduleType.getUID(), "Label cannot be blank");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@ package org.openhab.core.automation.internal.provider.file;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.automation.Module;
|
||||||
import org.openhab.core.automation.parser.Parser;
|
import org.openhab.core.automation.parser.Parser;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException;
|
||||||
|
import org.openhab.core.automation.parser.ValidationException.ObjectType;
|
||||||
import org.openhab.core.automation.template.RuleTemplate;
|
import org.openhab.core.automation.template.RuleTemplate;
|
||||||
import org.openhab.core.automation.template.RuleTemplateProvider;
|
import org.openhab.core.automation.template.RuleTemplateProvider;
|
||||||
import org.openhab.core.automation.template.TemplateProvider;
|
import org.openhab.core.automation.template.TemplateProvider;
|
||||||
|
@ -30,6 +33,7 @@ import org.osgi.service.component.annotations.ReferencePolicy;
|
||||||
* This class is a wrapper of multiple {@link TemplateProvider}s, responsible for initializing the WatchService.
|
* This class is a wrapper of multiple {@link TemplateProvider}s, responsible for initializing the WatchService.
|
||||||
*
|
*
|
||||||
* @author Ana Dimova - Initial contribution
|
* @author Ana Dimova - Initial contribution
|
||||||
|
* @author Arne Seime - Added template validation support
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
@Component(immediate = true, service = RuleTemplateProvider.class)
|
@Component(immediate = true, service = RuleTemplateProvider.class)
|
||||||
|
@ -63,4 +67,19 @@ public class TemplateFileProviderWatcher extends TemplateFileProvider {
|
||||||
public void removeParser(Parser<RuleTemplate> parser, Map<String, String> properties) {
|
public void removeParser(Parser<RuleTemplate> parser, Map<String, String> properties) {
|
||||||
super.removeParser(parser, properties);
|
super.removeParser(parser, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
@Override
|
||||||
|
protected void validateObject(RuleTemplate template) throws ValidationException {
|
||||||
|
String s;
|
||||||
|
if ((s = template.getUID()) == null || s.isBlank()) {
|
||||||
|
throw new ValidationException(ObjectType.TEMPLATE, null, "UID cannot be blank");
|
||||||
|
}
|
||||||
|
if ((s = template.getLabel()) == null || s.isBlank()) {
|
||||||
|
throw new ValidationException(ObjectType.TEMPLATE, template.getUID(), "Label cannot be blank");
|
||||||
|
}
|
||||||
|
if (template.getModules(Module.class).isEmpty()) {
|
||||||
|
throw new ValidationException(ObjectType.TEMPLATE, template.getUID(), "There must be at least one module");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,15 @@ public interface Parser<T> {
|
||||||
String FORMAT = "format";
|
String FORMAT = "format";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the possible value of property {@link #FORMAT}. It means that the parser supports json format.
|
* Defines a possible value of property {@link #FORMAT}. It means that the parser supports {@code JSON} format.
|
||||||
*/
|
*/
|
||||||
String FORMAT_JSON = "json";
|
String FORMAT_JSON = "json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a possible value of property {@link #FORMAT}. It means that the parser supports {@code YAML} format.
|
||||||
|
*/
|
||||||
|
String FORMAT_YAML = "yaml";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a file with some particular format and parse it to the corresponding automation objects.
|
* Loads a file with some particular format and parse it to the corresponding automation objects.
|
||||||
*
|
*
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class ParsingNestedException extends Exception {
|
||||||
* @param msg is the additional message with additional information about the parsing process.
|
* @param msg is the additional message with additional information about the parsing process.
|
||||||
* @param t is the exception thrown during the parsing.
|
* @param t is the exception thrown during the parsing.
|
||||||
*/
|
*/
|
||||||
public ParsingNestedException(int type, @Nullable String id, String msg, Throwable t) {
|
public ParsingNestedException(int type, @Nullable String id, String msg, @Nullable Throwable t) {
|
||||||
super(msg, t);
|
super(msg, t);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
@ -62,7 +62,7 @@ public class ParsingNestedException extends Exception {
|
||||||
* @param id is the UID of the automation object for parsing.
|
* @param id is the UID of the automation object for parsing.
|
||||||
* @param t is the exception thrown during the parsing.
|
* @param t is the exception thrown during the parsing.
|
||||||
*/
|
*/
|
||||||
public ParsingNestedException(int type, @Nullable String id, Throwable t) {
|
public ParsingNestedException(int type, @Nullable String id, @Nullable Throwable t) {
|
||||||
super(t);
|
super(t);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2010-2025 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.automation.parser;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an {@link Exception} implementation for automation objects that retain some additional information.
|
||||||
|
*
|
||||||
|
* @author Arne Seime - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ValidationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps information about the type of the automation object for validation - module type, template or rule.
|
||||||
|
*/
|
||||||
|
private final ObjectType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps information about the UID of the automation object for validation - module type, template or rule.
|
||||||
|
*/
|
||||||
|
private final @Nullable String uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the specified type, UID and message.
|
||||||
|
*
|
||||||
|
* @param type the {@link ObjectType} to use.
|
||||||
|
* @param uid the UID to use, if any.
|
||||||
|
* @param message The detail message.
|
||||||
|
*/
|
||||||
|
public ValidationException(ObjectType type, @Nullable String uid, @Nullable String message) {
|
||||||
|
super(message);
|
||||||
|
this.type = type;
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the specified type, UID and cause.
|
||||||
|
*
|
||||||
|
* @param type the {@link ObjectType} to use.
|
||||||
|
* @param uid the UID to use, if any.
|
||||||
|
* @param cause the {@link Throwable} that caused this {@link Exception}.
|
||||||
|
*/
|
||||||
|
public ValidationException(ObjectType type, @Nullable String uid, @Nullable Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
this.type = type;
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the specified type, UID, message and cause.
|
||||||
|
*
|
||||||
|
* @param type the {@link ObjectType} to use.
|
||||||
|
* @param uid the UID to use, if any.
|
||||||
|
* @param message The detail message.
|
||||||
|
* @param cause the {@link Throwable} that caused this {@link Exception}.
|
||||||
|
*/
|
||||||
|
public ValidationException(ObjectType type, @Nullable String uid, @Nullable String message,
|
||||||
|
@Nullable Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.type = type;
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the specified type, UID, message and cause.
|
||||||
|
*
|
||||||
|
* @param type the {@link ObjectType} to use.
|
||||||
|
* @param uid the UID to use, if any.
|
||||||
|
* @param message The detail message.
|
||||||
|
* @param cause the {@link Throwable} that caused this {@link Exception}.
|
||||||
|
* @param enableSuppression whether or not suppression is enabled or disabled.
|
||||||
|
* @param writableStackTrace whether or not the stack trace should be writable.
|
||||||
|
*/
|
||||||
|
public ValidationException(ObjectType type, @Nullable String uid, @Nullable String message,
|
||||||
|
@Nullable Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
this.type = type;
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getMessage() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
switch (type) {
|
||||||
|
case MODULE_TYPE:
|
||||||
|
sb.append("[Module Type");
|
||||||
|
break;
|
||||||
|
case TEMPLATE:
|
||||||
|
sb.append("[Template");
|
||||||
|
break;
|
||||||
|
case RULE:
|
||||||
|
sb.append("[Rule");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (uid != null) {
|
||||||
|
sb.append(' ').append(uid);
|
||||||
|
}
|
||||||
|
sb.append("] ").append(super.getMessage());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ObjectType {
|
||||||
|
MODULE_TYPE,
|
||||||
|
TEMPLATE,
|
||||||
|
RULE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,7 +73,7 @@ public class ConfigDispatcherFileWatcher implements WatchService.WatchEventListe
|
||||||
} else if (kind == WatchService.Kind.DELETE) {
|
} else if (kind == WatchService.Kind.DELETE) {
|
||||||
// Detect if a service specific configuration file was removed. We want to
|
// Detect if a service specific configuration file was removed. We want to
|
||||||
// notify the service in this case with an updated empty configuration.
|
// notify the service in this case with an updated empty configuration.
|
||||||
if (Files.isHidden(fullPath) || Files.isDirectory(fullPath) || !fullPath.toString().endsWith(".cfg")) {
|
if (!fullPath.toString().endsWith(".cfg")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
configDispatcher.fileRemoved(fullPath.toString());
|
configDispatcher.fileRemoved(fullPath.toString());
|
||||||
|
|
|
@ -135,8 +135,7 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
|
||||||
public synchronized void processWatchEvent(Kind kind, Path path) {
|
public synchronized void processWatchEvent(Kind kind, Path path) {
|
||||||
Path fullPath = watchPath.resolve(path);
|
Path fullPath = watchPath.resolve(path);
|
||||||
String pathString = path.toString();
|
String pathString = path.toString();
|
||||||
if (!Files.isReadable(fullPath) || Files.isDirectory(fullPath) || path.startsWith("automation")
|
if (path.startsWith("automation") || !pathString.endsWith(".yaml")) {
|
||||||
|| !pathString.endsWith(".yaml") || fullPath.toFile().isHidden()) {
|
|
||||||
logger.trace("Ignored {}", fullPath);
|
logger.trace("Ignored {}", fullPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -144,29 +143,29 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
|
||||||
// strip extension for model name
|
// strip extension for model name
|
||||||
String modelName = pathString.substring(0, pathString.lastIndexOf("."));
|
String modelName = pathString.substring(0, pathString.lastIndexOf("."));
|
||||||
|
|
||||||
if (kind == WatchService.Kind.DELETE) {
|
try {
|
||||||
logger.info("Removing YAML model {}", modelName);
|
if (kind == WatchService.Kind.DELETE) {
|
||||||
YamlModelWrapper removedModel = modelCache.remove(modelName);
|
logger.info("Removing YAML model {}", modelName);
|
||||||
if (removedModel == null) {
|
YamlModelWrapper removedModel = modelCache.remove(modelName);
|
||||||
return;
|
if (removedModel == null) {
|
||||||
}
|
return;
|
||||||
for (Map.Entry<String, List<JsonNode>> modelEntry : removedModel.getNodes().entrySet()) {
|
}
|
||||||
String elementName = modelEntry.getKey();
|
for (Map.Entry<String, List<JsonNode>> modelEntry : removedModel.getNodes().entrySet()) {
|
||||||
List<JsonNode> removedNodes = modelEntry.getValue();
|
String elementName = modelEntry.getKey();
|
||||||
if (!removedNodes.isEmpty()) {
|
List<JsonNode> removedNodes = modelEntry.getValue();
|
||||||
getElementListeners(elementName).forEach(listener -> {
|
if (!removedNodes.isEmpty()) {
|
||||||
List removedElements = parseJsonNodes(removedNodes, listener.getElementClass());
|
getElementListeners(elementName).forEach(listener -> {
|
||||||
listener.removedModel(modelName, removedElements);
|
List removedElements = parseJsonNodes(removedNodes, listener.getElementClass());
|
||||||
});
|
listener.removedModel(modelName, removedElements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!Files.isHidden(fullPath) && Files.isReadable(fullPath) && !Files.isDirectory(fullPath)) {
|
||||||
|
if (kind == Kind.CREATE) {
|
||||||
|
logger.info("Adding YAML model {}", modelName);
|
||||||
|
} else {
|
||||||
|
logger.info("Updating YAML model {}", modelName);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (kind == Kind.CREATE) {
|
|
||||||
logger.info("Adding YAML model {}", modelName);
|
|
||||||
} else {
|
|
||||||
logger.info("Updating YAML model {}", modelName);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JsonNode fileContent = objectMapper.readTree(fullPath.toFile());
|
JsonNode fileContent = objectMapper.readTree(fullPath.toFile());
|
||||||
|
|
||||||
// check version
|
// check version
|
||||||
|
@ -233,9 +232,11 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
|
||||||
// replace cache
|
// replace cache
|
||||||
model.getNodes().put(elementName, newNodeElements);
|
model.getNodes().put(elementName, newNodeElements);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} else {
|
||||||
logger.warn("Failed to read {}: {}", modelName, e.getMessage());
|
logger.trace("Ignored {}", fullPath);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to process model {}: {}", modelName, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,4 +81,6 @@ Fragment-Host: org.openhab.core.automation
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -81,4 +81,6 @@ Fragment-Host: org.openhab.core.automation
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -78,4 +78,6 @@ Fragment-Host: org.openhab.core.automation.module.script
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -81,4 +81,6 @@ Fragment-Host: org.openhab.core.automation
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -81,4 +81,6 @@ Fragment-Host: org.openhab.core.automation
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -128,4 +128,6 @@ Fragment-Host: org.openhab.core.model.item
|
||||||
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -130,4 +130,6 @@ Fragment-Host: org.openhab.core.model.rule.runtime
|
||||||
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
org.osgi.service.cm;version='[1.6.0,1.6.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -134,4 +134,6 @@ Fragment-Host: org.openhab.core.model.script
|
||||||
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
|
@ -137,4 +137,6 @@ Fragment-Host: org.openhab.core.model.thing
|
||||||
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
org.openhab.core.model.rule.runtime;version='[5.0.0,5.0.1)',\
|
||||||
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-core;version='[1.4.0,1.4.1)',\
|
||||||
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
de.focus_shift.jollyday-jackson;version='[1.4.0,1.4.1)',\
|
||||||
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)'
|
org.ops4j.pax.logging.pax-logging-api;version='[2.2.8,2.2.9)',\
|
||||||
|
com.fasterxml.jackson.dataformat.jackson-dataformat-yaml;version='[2.18.2,2.18.3)',\
|
||||||
|
org.yaml.snakeyaml;version='[2.3.0,2.3.1)'
|
||||||
|
|
Loading…
Reference in New Issue