mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-31 05:24:26 +02:00
Rule and RuleTemplate file format conversion (#5572)
* Create ObjectParser and ObjectSerializer interfaces for rules and rule templates Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Embed DSL rule source with the Rule Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Detect shared context DSL rules Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Update YAML DTOs to support serialization Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Create rule converters Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Create rule template YAML converter Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Implement rule and rule template conversion in the REST API Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Handle DSL conditions Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Create check serializability endpoint Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Don't deserialize TemplateState - deduce it from templateUID Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Add GenericEventTrigger to ModuleTypeAliases Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Fix JavaDoc errors Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Allow triggerless DSL rules Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Fix negative indentation issue during DSL formatting Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Add rule configuration to "rule summary" Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Fix rule template REST API schema Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Address review feedback Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Address review feedback Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Handle single digit hour specification during DSL serialization Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Address review comments Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Address review comments Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Apply review suggestion Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Remove seemingly needless import Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Apply review derived fixes Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Review fixes Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Address review formatter feedback Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> * Fix YAML bundle feature dependency Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> --------- Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com> Co-authored-by: Ravi Nadahar <nadahar@rediffmail.com>
This commit is contained in:
+1
-1
@@ -213,7 +213,7 @@ public class RuleResource implements RESTResource {
|
||||
.map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); // map matching rules
|
||||
if (summary != null && summary) {
|
||||
rules = dtoMapper.limitToFields(rules,
|
||||
"uid,templateUID,templateState,name,visibility,description,status,tags,editable");
|
||||
"uid,templateUID,templateState,name,visibility,description,status,tags,configuration,editable");
|
||||
}
|
||||
|
||||
return Response.ok(new Stream2JSONInputStream(rules)).build();
|
||||
|
||||
+4
-6
@@ -24,13 +24,11 @@ import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
||||
import org.openhab.core.automation.dto.RuleTemplateDTOMapper;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.automation.template.Template;
|
||||
import org.openhab.core.automation.template.TemplateRegistry;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
@@ -73,12 +71,12 @@ public class TemplateResource implements RESTResource {
|
||||
public static final String PATH_TEMPLATES = "templates";
|
||||
|
||||
private final LocaleService localeService;
|
||||
private final TemplateRegistry<@NonNull RuleTemplate> templateRegistry;
|
||||
private final TemplateRegistry<RuleTemplate> templateRegistry;
|
||||
|
||||
@Activate
|
||||
public TemplateResource( //
|
||||
final @Reference LocaleService localeService,
|
||||
final @Reference TemplateRegistry<@NonNull RuleTemplate> templateRegistry) {
|
||||
final @Reference TemplateRegistry<RuleTemplate> templateRegistry) {
|
||||
this.localeService = localeService;
|
||||
this.templateRegistry = templateRegistry;
|
||||
}
|
||||
@@ -86,7 +84,7 @@ public class TemplateResource implements RESTResource {
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getTemplates", summary = "Get all available templates.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Template.class)))) })
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RuleTemplateDTO.class)))) })
|
||||
public Response getAll(
|
||||
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) {
|
||||
Locale locale = localeService.getLocale(language);
|
||||
@@ -99,7 +97,7 @@ public class TemplateResource implements RESTResource {
|
||||
@Path("/{templateUID}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getTemplateById", summary = "Gets a template corresponding to the given UID.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Template.class))),
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RuleTemplateDTO.class))),
|
||||
@ApiResponse(responseCode = "404", description = "Template corresponding to the given UID does not found.") })
|
||||
public Response getByUID(
|
||||
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language,
|
||||
|
||||
+7
@@ -77,4 +77,11 @@ public interface Module {
|
||||
* @return the current configuration values of the {@link Module}.
|
||||
*/
|
||||
Configuration getConfiguration();
|
||||
|
||||
/**
|
||||
* The "magic key" used for {@link Module}s that depend on shared context that isn't part of the {@link Module}
|
||||
* object itself, for example the {@link Action}s of DSL rules with definitions outside the {@code rule} section.
|
||||
* The information is stored in the {@link Module}'s {@link Configuration}. The payload is a {@link Boolean}.
|
||||
*/
|
||||
static String SHARED_CONTEXT = "sharedContext";
|
||||
}
|
||||
|
||||
+14
@@ -167,6 +167,20 @@ public interface Rule extends Identifiable<String> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "magic key" used for the source code string itself when storing {@link Rule}'s source code in its
|
||||
* {@link Configuration}. A {@link Rule} can only have a source code if it is created by a script. The payload
|
||||
* is a string containing the whole source code.
|
||||
*/
|
||||
static String SOURCE = "source";
|
||||
|
||||
/**
|
||||
* The "magic key" used for the source code type when storing {@link Rule}'s source code in its
|
||||
* {@link Configuration}. A {@link Rule} can only have a source code if it is created by a script. The payload
|
||||
* is a string containing OH's "quasi MIME-type" for the scripting language of the source code.
|
||||
*/
|
||||
static String SOURCE_TYPE = "sourceType";
|
||||
|
||||
/**
|
||||
* This enum represent the different states a rule can have in respect to rule templates.
|
||||
*/
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.converter.ObjectParser;
|
||||
|
||||
/**
|
||||
* {@link RuleParser} is the interface to implement by any file parser for {@link Rule} object.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RuleParser extends ObjectParser<Rule> {
|
||||
|
||||
/**
|
||||
* Parse the provided {@code syntax} string without impacting the rule registry.
|
||||
*
|
||||
* @param syntax the syntax in format.
|
||||
* @param errors the {@link List} to use to report errors.
|
||||
* @param warnings the {@link List} to be used to report warnings.
|
||||
* @return The model name used for parsing if the parsing succeeded without errors; {@code null} otherwise.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
String startParsingFormat(String syntax, List<String> errors, List<String> warnings);
|
||||
|
||||
/**
|
||||
* Get the {@link Rule} objects found when parsing the format.
|
||||
*
|
||||
* @param modelName the model name used when parsing.
|
||||
* @return The {@link Collection} of {@link Rule}s.
|
||||
*/
|
||||
@Override
|
||||
Collection<Rule> getParsedObjects(String modelName);
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.converter.ObjectSerializer;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
|
||||
/**
|
||||
* {@link RuleSerializer} is the interface to implement by any file generator for {@link Rule} object.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RuleSerializer extends ObjectSerializer<Rule> {
|
||||
|
||||
/**
|
||||
* Checks if the specified rules are serializable with this {@link RuleSerializer}. Returned results are in the same
|
||||
* order as the specified rules.
|
||||
*
|
||||
* @param rules the {@link List} of {@link Rule}s to check.
|
||||
* @return The resulting {@link List} of {@link SerializabilityResult}s.
|
||||
*/
|
||||
List<SerializabilityResult<String>> checkSerializability(Collection<Rule> rules);
|
||||
|
||||
/**
|
||||
* Specify the {@link List} of {@link Rule}s to be serialized and associate them with an identifier.
|
||||
*
|
||||
* @param id the identifier of the {@link Rule} format generation.
|
||||
* @param rules the {@link List} of {@link Rule}s to serialize.
|
||||
* @param option the option that determines how to serialize the {@link Rule}s.
|
||||
* @throws SerializationException If one or more of the rules can't be serialized.
|
||||
*/
|
||||
void setRulesToBeSerialized(String id, List<Rule> rules, RuleSerializationOption option)
|
||||
throws SerializationException;
|
||||
|
||||
/**
|
||||
* An enum representing the different rule serialization options.
|
||||
*/
|
||||
public enum RuleSerializationOption {
|
||||
|
||||
/** Empty collections and normally irrelevant fields are hidden */
|
||||
NORMAL("Normal"),
|
||||
|
||||
/** Everything is serialized, including empty collections */
|
||||
INCLUDE_ALL("Include all"),
|
||||
|
||||
/** Only the fields required in a rule stub to be used with a template is serialized */
|
||||
STUB_ONLY("Stub only"),
|
||||
|
||||
/** Template information is stripped, otherwise like {@link #NORMAL} */
|
||||
STRIP_TEMPLATE("Strip template");
|
||||
|
||||
private final String friendlyName;
|
||||
|
||||
private RuleSerializationOption(String friendlyName) {
|
||||
this.friendlyName = friendlyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
public static @Nullable RuleSerializationOption fromString(@Nullable String id) {
|
||||
if (id == null || id.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String upId = id.toUpperCase(Locale.ROOT).trim();
|
||||
for (RuleSerializationOption option : values()) {
|
||||
if (upId.equals(option.name()) || upId.equalsIgnoreCase(option.friendlyName)
|
||||
|| upId.equalsIgnoreCase(option.friendlyName.replace(" ", ""))
|
||||
|| upId.equalsIgnoreCase(option.friendlyName.replace(" ", "-"))) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.converter.ObjectParser;
|
||||
|
||||
/**
|
||||
* {@link RuleTemplateParser} is the interface to implement by any file parser for {@link RuleTemplate} object.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RuleTemplateParser extends ObjectParser<RuleTemplate> {
|
||||
|
||||
/**
|
||||
* Parse the provided {@code syntax} string without impacting the rule template registry.
|
||||
*
|
||||
* @param syntax the syntax in format.
|
||||
* @param errors the {@link List} to use to report errors.
|
||||
* @param warnings the {@link List} to be used to report warnings.
|
||||
* @return The model name used for parsing if the parsing succeeded without errors; {@code null} otherwise.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
String startParsingFormat(String syntax, List<String> errors, List<String> warnings);
|
||||
|
||||
/**
|
||||
* Get the {@link RuleTemplate} objects found when parsing the format.
|
||||
*
|
||||
* @param modelName the model name used when parsing.
|
||||
* @return The {@link Collection} of {@link RuleTemplate}s.
|
||||
*/
|
||||
@Override
|
||||
Collection<RuleTemplate> getParsedObjects(String modelName);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.converter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.converter.ObjectSerializer;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
|
||||
/**
|
||||
* {@link RuleTemplateSerializer} is the interface to implement by any file generator for {@link RuleTemplate} object.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RuleTemplateSerializer extends ObjectSerializer<RuleTemplate> {
|
||||
|
||||
/**
|
||||
* Checks if the specified rule templates are serializable with this {@link RuleTemplateSerializer}. Returned
|
||||
* results are in the same order as the specified rule templates.
|
||||
*
|
||||
* @param templates the {@link List} of {@link RuleTemplate}s to check.
|
||||
* @return The resulting {@link List} of {@link SerializabilityResult}s.
|
||||
*/
|
||||
List<SerializabilityResult<String>> checkSerializability(Collection<RuleTemplate> templates);
|
||||
|
||||
/**
|
||||
* Specify the {@link List} of {@link RuleTemplate}s to be serialized and associate them with an identifier.
|
||||
*
|
||||
* @param id the identifier of the {@link RuleTemplate} format generation.
|
||||
* @param templates the {@link List} of {@link RuleTemplate}s to serialize.
|
||||
* @param option the option that determines how to serialize the {@link RuleTemplate}s.
|
||||
* @throws SerializationException If one or more of the rule templates can't be serialized.
|
||||
*/
|
||||
void setTemplatesToBeSerialized(String id, List<RuleTemplate> templates, RuleTemplateSerializationOption option)
|
||||
throws SerializationException;
|
||||
|
||||
/**
|
||||
* An enum representing the different rule template serialization options.
|
||||
*/
|
||||
public enum RuleTemplateSerializationOption {
|
||||
|
||||
/** Empty collections and normally irrelevant fields are hidden */
|
||||
NORMAL("Normal"),
|
||||
|
||||
/** Everything is serialized, including empty collections */
|
||||
INCLUDE_ALL("Include all");
|
||||
|
||||
private final String friendlyName;
|
||||
|
||||
private RuleTemplateSerializationOption(String friendlyName) {
|
||||
this.friendlyName = friendlyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this {@link RuleTemplateSerializationOption} to a corresponding {@link RuleSerializationOption}.
|
||||
*
|
||||
* @return The corresponding {@link RuleSerializationOption}.
|
||||
*/
|
||||
public RuleSerializationOption toRuleSerializationOption() {
|
||||
switch (this) {
|
||||
case INCLUDE_ALL:
|
||||
return RuleSerializationOption.INCLUDE_ALL;
|
||||
case NORMAL:
|
||||
return RuleSerializationOption.NORMAL;
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Missing toRuleSerializationOption() implementation for " + name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the specified string into a {@link RuleTemplateSerializationOption}.
|
||||
*
|
||||
* @param id The {@link String} to parse.
|
||||
* @return The resulting {@link RuleTemplateSerializationOption} or {@code null}.
|
||||
*/
|
||||
public static @Nullable RuleTemplateSerializationOption fromString(@Nullable String id) {
|
||||
if (id == null || id.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String upId = id.toUpperCase(Locale.ROOT).trim();
|
||||
for (RuleTemplateSerializationOption option : values()) {
|
||||
if (upId.equals(option.name()) || upId.equalsIgnoreCase(option.friendlyName)
|
||||
|| upId.equalsIgnoreCase(option.friendlyName.replace(" ", ""))
|
||||
|| upId.equalsIgnoreCase(option.friendlyName.replace(" ", "-"))) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -60,8 +60,10 @@ public class RuleBuilder {
|
||||
this.actions = new LinkedList<>(rule.getActions());
|
||||
this.configuration = new Configuration(rule.getConfiguration());
|
||||
this.configDescriptions = new LinkedList<>(rule.getConfigurationDescriptions());
|
||||
this.templateUID = rule.getTemplateUID();
|
||||
this.templateState = TemplateState.NO_TEMPLATE;
|
||||
String templateUID = rule.getTemplateUID();
|
||||
this.templateUID = templateUID;
|
||||
this.templateState = templateUID == null || templateUID.isBlank() ? TemplateState.NO_TEMPLATE
|
||||
: TemplateState.PENDING;
|
||||
this.uid = rule.getUID();
|
||||
this.name = rule.getName();
|
||||
this.tags = new HashSet<>(rule.getTags());
|
||||
@@ -87,7 +89,7 @@ public class RuleBuilder {
|
||||
* @return The new {@link RuleBuilder}.
|
||||
*/
|
||||
public static RuleBuilder create(Rule r) {
|
||||
return create(r.getUID(), r);
|
||||
return new RuleBuilder(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +110,7 @@ public class RuleBuilder {
|
||||
/**
|
||||
* Build a new {@link Rule} from the specified {@link RuleTemplate} and {@link Configuration}. The resulting rule
|
||||
* will be ready for template placeholder substitution, but the placeholders won't actually have been substituted.
|
||||
* This constructor is only suitable for preparing to substitute placeholders.
|
||||
* This method is only suitable for preparing to substitute placeholders.
|
||||
*
|
||||
* @param template the {@link RuleTemplate} to use.
|
||||
* @param uid the UID of the resulting {@link Rule}.
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
<artifactId>org.openhab.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.automation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.config.core</artifactId>
|
||||
|
||||
+7
-1
@@ -14,6 +14,8 @@ package org.openhab.core.io.rest.core.fileformat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.core.automation.dto.RuleDTO;
|
||||
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
||||
import org.openhab.core.semantics.dto.SemanticTagDTO;
|
||||
import org.openhab.core.sitemap.dto.SitemapDefinitionDTO;
|
||||
import org.openhab.core.thing.dto.ThingDTO;
|
||||
@@ -22,7 +24,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* This is a data transfer object to serialize the different components that can be contained
|
||||
* in a file format (items, things, ...).
|
||||
* in a file format (items, things, rules, ...).
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Mark Herwege - Add sitemaps
|
||||
@@ -39,4 +41,8 @@ public class FileFormatDTO {
|
||||
public List<ThingDTO> things;
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
public List<SitemapDefinitionDTO> sitemaps;
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
public List<RuleDTO> rules;
|
||||
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
public List<RuleTemplateDTO> ruleTemplates;
|
||||
}
|
||||
|
||||
+602
-15
@@ -15,6 +15,7 @@ package org.openhab.core.io.rest.core.internal.fileformat;
|
||||
import static org.openhab.core.config.discovery.inbox.InboxPredicates.forThingUID;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -23,9 +24,11 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -41,10 +44,26 @@ import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status.Family;
|
||||
import javax.ws.rs.core.Response.StatusType;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.RuleRegistry;
|
||||
import org.openhab.core.automation.converter.RuleParser;
|
||||
import org.openhab.core.automation.converter.RuleSerializer;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
import org.openhab.core.automation.converter.RuleTemplateParser;
|
||||
import org.openhab.core.automation.converter.RuleTemplateSerializer;
|
||||
import org.openhab.core.automation.converter.RuleTemplateSerializer.RuleTemplateSerializationOption;
|
||||
import org.openhab.core.automation.dto.RuleDTO;
|
||||
import org.openhab.core.automation.dto.RuleDTOMapper;
|
||||
import org.openhab.core.automation.dto.RuleTemplateDTO;
|
||||
import org.openhab.core.automation.dto.RuleTemplateDTOMapper;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.automation.template.TemplateRegistry;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
@@ -53,12 +72,15 @@ import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.inbox.Inbox;
|
||||
import org.openhab.core.converter.ObjectParser;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.core.fileformat.ExtendedFileFormatDTO;
|
||||
import org.openhab.core.io.rest.core.fileformat.FileFormatDTO;
|
||||
import org.openhab.core.io.rest.core.fileformat.FileFormatItemDTO;
|
||||
import org.openhab.core.io.rest.core.fileformat.FileFormatItemDTOMapper;
|
||||
import org.openhab.core.io.rest.core.internal.rule.SerializabilityResultsDTO;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemBuilderFactory;
|
||||
@@ -68,6 +90,7 @@ import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.items.fileconverter.ItemParser;
|
||||
import org.openhab.core.items.fileconverter.ItemSerializer;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.semantics.SemanticTag;
|
||||
import org.openhab.core.semantics.SemanticTagRegistry;
|
||||
import org.openhab.core.semantics.dto.SemanticTagDTO;
|
||||
@@ -117,9 +140,15 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -215,6 +244,137 @@ public class FileFormatResource implements RESTResource {
|
||||
param: my param value
|
||||
""";
|
||||
|
||||
private static final String DSL_RULE_EXAMPLE = """
|
||||
rule "My Rule"
|
||||
when
|
||||
Time is noon
|
||||
then
|
||||
logInfo("Test", "MyRule is running")
|
||||
end
|
||||
""";
|
||||
|
||||
private static final String YAML_RULE_EXAMPLE = """
|
||||
version: 1
|
||||
rules:
|
||||
MyRule:
|
||||
label: My Rule
|
||||
description: My rule description
|
||||
actions:
|
||||
- id: "2"
|
||||
config:
|
||||
type: DSL
|
||||
script: |
|
||||
logInfo("Test", "MyRule is running")
|
||||
type: Script
|
||||
triggers:
|
||||
- id: "1"
|
||||
config:
|
||||
time: 12:00
|
||||
type: TimeOfDay
|
||||
""";
|
||||
|
||||
private static final String JSON_RULE_UID_CHECK_EXAMPLE = """
|
||||
[ "ruleUid1", "ruleUid2" ]
|
||||
""";
|
||||
|
||||
private static final String JSON_RULE_CHECK_EXAMPLE = """
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"uid": "string",
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"tags": [
|
||||
"string"
|
||||
],
|
||||
"triggers": [
|
||||
{
|
||||
"id": "string",
|
||||
"label": "string",
|
||||
"description": "string",
|
||||
"configuration": {
|
||||
"additionalProp1": {},
|
||||
"additionalProp2": {},
|
||||
"additionalProp3": {}
|
||||
},
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"id": "string",
|
||||
"label": "string",
|
||||
"description": "string",
|
||||
"configuration": {
|
||||
"additionalProp1": {},
|
||||
"additionalProp2": {},
|
||||
"additionalProp3": {}
|
||||
},
|
||||
"type": "string",
|
||||
"inputs": {
|
||||
"additionalProp1": "string",
|
||||
"additionalProp2": "string",
|
||||
"additionalProp3": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "string",
|
||||
"label": "string",
|
||||
"description": "string",
|
||||
"configuration": {
|
||||
"additionalProp1": {},
|
||||
"additionalProp2": {},
|
||||
"additionalProp3": {}
|
||||
},
|
||||
"type": "string",
|
||||
"inputs": {
|
||||
"additionalProp1": "string",
|
||||
"additionalProp2": "string",
|
||||
"additionalProp3": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String YAML_RULE_TEMPLATE_EXAMPLE = """
|
||||
version: 1
|
||||
ruleTemplates:
|
||||
my-template:
|
||||
label: My Template
|
||||
description: Logs when an Item state changes to ON
|
||||
configDescriptions:
|
||||
sourceItem:
|
||||
context: item
|
||||
description: The source Item whose state to monitor
|
||||
label: Source Item
|
||||
required: false
|
||||
type: TEXT
|
||||
readOnly: false
|
||||
multiple: false
|
||||
advanced: false
|
||||
verify: false
|
||||
limitToOptions: true
|
||||
actions:
|
||||
- id: "2"
|
||||
config:
|
||||
type: DSL
|
||||
script: |
|
||||
logInfo("Test", "{{sourceItem}} turned on")
|
||||
type: Script
|
||||
triggers:
|
||||
- id: "1"
|
||||
config:
|
||||
itemName: "{{sourceItem}}"
|
||||
state: "ON"
|
||||
previousState: "OFF"
|
||||
type: ItemChanged
|
||||
""";
|
||||
|
||||
private static final String DSL_SITEMAPS_EXAMPLE = """
|
||||
sitemap MySitemap label="My Sitemap" {
|
||||
Frame {
|
||||
@@ -297,6 +457,49 @@ public class FileFormatResource implements RESTResource {
|
||||
value: my value
|
||||
config:
|
||||
param: my param value
|
||||
rules:
|
||||
MyRule:
|
||||
label: Label
|
||||
actions:
|
||||
- config:
|
||||
type: DSL
|
||||
script: |
|
||||
logInfo("Test", "MyRule is running")
|
||||
type: Script
|
||||
triggers:
|
||||
- config:
|
||||
itemName: MyItem
|
||||
type: ItemReceivedCommand
|
||||
ruleTemplates:
|
||||
my-template:
|
||||
label: My Template
|
||||
description: Logs when an Item state changes to ON
|
||||
configDescriptions:
|
||||
sourceItem:
|
||||
context: item
|
||||
description: The source Item whose state to monitor
|
||||
label: Source Item
|
||||
required: false
|
||||
type: TEXT
|
||||
readOnly: false
|
||||
multiple: false
|
||||
advanced: false
|
||||
verify: false
|
||||
limitToOptions: true
|
||||
actions:
|
||||
- id: "2"
|
||||
config:
|
||||
type: DSL
|
||||
script: |
|
||||
logInfo("Test", "{{sourceItem}} turned on")
|
||||
type: Script
|
||||
triggers:
|
||||
- id: "1"
|
||||
config:
|
||||
itemName: "{{sourceItem}}"
|
||||
state: "ON"
|
||||
previousState: "OFF"
|
||||
type: ItemChanged
|
||||
sitemaps:
|
||||
MySitemap:
|
||||
label: My Sitemap
|
||||
@@ -310,8 +513,27 @@ public class FileFormatResource implements RESTResource {
|
||||
|
||||
private static final String GEN_ID_PATTERN = "gen_file_format_%d";
|
||||
|
||||
private static final Response.StatusType UNPROCESSABLE_ENTITY = new StatusType() {
|
||||
|
||||
@Override
|
||||
public int getStatusCode() {
|
||||
return 422;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReasonPhrase() {
|
||||
return "Unprocessable Entity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Family getFamily() {
|
||||
return Family.CLIENT_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FileFormatResource.class);
|
||||
|
||||
private final Gson gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_JSON_COMPAT).create();
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
private final ItemBuilderFactory itemBuilderFactory;
|
||||
private final ItemRegistry itemRegistry;
|
||||
@@ -322,6 +544,8 @@ public class FileFormatResource implements RESTResource {
|
||||
private final ThingTypeRegistry thingTypeRegistry;
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
private final ConfigDescriptionRegistry configDescRegistry;
|
||||
private final RuleRegistry ruleRegistry;
|
||||
private final TemplateRegistry<RuleTemplate> templateRegistry;
|
||||
private final SitemapFactory sitemapFactory;
|
||||
private final SitemapRegistry sitemapRegistry;
|
||||
private final Map<String, SemanticTagSerializer> tagSerializers = new ConcurrentHashMap<>();
|
||||
@@ -330,10 +554,14 @@ public class FileFormatResource implements RESTResource {
|
||||
private final Map<String, ItemParser> itemParsers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ThingSerializer> thingSerializers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ThingParser> thingParsers = new ConcurrentHashMap<>();
|
||||
private final Map<String, RuleSerializer> ruleSerializers = new ConcurrentHashMap<>();
|
||||
private final Map<String, RuleParser> ruleParsers = new ConcurrentHashMap<>();
|
||||
private final Map<String, RuleTemplateSerializer> templateSerializers = new ConcurrentHashMap<>();
|
||||
private final Map<String, RuleTemplateParser> templateParsers = new ConcurrentHashMap<>();
|
||||
private final Map<String, SitemapSerializer> sitemapSerializers = new ConcurrentHashMap<>();
|
||||
private final Map<String, SitemapParser> sitemapParsers = new ConcurrentHashMap<>();
|
||||
|
||||
private int counter;
|
||||
private final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
@Activate
|
||||
public FileFormatResource(//
|
||||
@@ -347,6 +575,8 @@ public class FileFormatResource implements RESTResource {
|
||||
final @Reference ThingTypeRegistry thingTypeRegistry, //
|
||||
final @Reference ChannelTypeRegistry channelTypeRegistry, //
|
||||
final @Reference ConfigDescriptionRegistry configDescRegistry, //
|
||||
final @Reference RuleRegistry ruleRegistry, //
|
||||
final @Reference TemplateRegistry<RuleTemplate> templateRegistry, //
|
||||
final @Reference SitemapFactory sitemapFactory, //
|
||||
final @Reference SitemapRegistry sitemapRegistry) {
|
||||
this.semanticTagRegistry = semanticTagRegistry;
|
||||
@@ -359,6 +589,8 @@ public class FileFormatResource implements RESTResource {
|
||||
this.thingTypeRegistry = thingTypeRegistry;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
this.configDescRegistry = configDescRegistry;
|
||||
this.ruleRegistry = ruleRegistry;
|
||||
this.templateRegistry = templateRegistry;
|
||||
this.sitemapFactory = sitemapFactory;
|
||||
this.sitemapRegistry = sitemapRegistry;
|
||||
}
|
||||
@@ -421,6 +653,42 @@ public class FileFormatResource implements RESTResource {
|
||||
thingParsers.remove(thingParser.getParserFormat());
|
||||
}
|
||||
|
||||
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
|
||||
protected void addRuleSerializer(RuleSerializer ruleSerializer) {
|
||||
ruleSerializers.put(ruleSerializer.getGeneratedFormat(), ruleSerializer);
|
||||
}
|
||||
|
||||
protected void removeRuleSerializer(RuleSerializer ruleSerializer) {
|
||||
ruleSerializers.remove(ruleSerializer.getGeneratedFormat());
|
||||
}
|
||||
|
||||
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
|
||||
protected void addRuleParser(RuleParser ruleParser) {
|
||||
ruleParsers.put(ruleParser.getParserFormat(), ruleParser);
|
||||
}
|
||||
|
||||
protected void removeRuleParser(RuleParser ruleParser) {
|
||||
ruleParsers.remove(ruleParser.getParserFormat());
|
||||
}
|
||||
|
||||
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
|
||||
protected void addRuleTemplateSerializer(RuleTemplateSerializer templateSerializer) {
|
||||
templateSerializers.put(templateSerializer.getGeneratedFormat(), templateSerializer);
|
||||
}
|
||||
|
||||
protected void removeRuleTemplateSerializer(RuleTemplateSerializer templateSerializer) {
|
||||
templateSerializers.remove(templateSerializer.getGeneratedFormat());
|
||||
}
|
||||
|
||||
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
|
||||
protected void addRuleTemplateParser(RuleTemplateParser templateParser) {
|
||||
templateParsers.put(templateParser.getParserFormat(), templateParser);
|
||||
}
|
||||
|
||||
protected void removeRuleTemplateParser(RuleTemplateParser templateParser) {
|
||||
templateParsers.remove(templateParser.getParserFormat());
|
||||
}
|
||||
|
||||
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE)
|
||||
protected void addSitemapSerializer(SitemapSerializer sitemapSerializer) {
|
||||
sitemapSerializers.put(sitemapSerializer.getGeneratedFormat(), sitemapSerializer);
|
||||
@@ -529,6 +797,170 @@ public class FileFormatResource implements RESTResource {
|
||||
return Response.ok(outputStream.toString(StandardCharsets.UTF_8)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/rules")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces({ "application/vnd.openhab.dsl.rule", "application/yaml", MediaType.APPLICATION_JSON })
|
||||
@Operation(operationId = "createFileFormatForRules", summary = "Create file format for a list of rules in the registry.", security = {
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = {
|
||||
@Content(mediaType = "application/vnd.openhab.dsl.rule", schema = @Schema(example = DSL_RULE_EXAMPLE)),
|
||||
@Content(mediaType = "application/yaml", schema = @Schema(example = YAML_RULE_EXAMPLE)) }),
|
||||
@ApiResponse(responseCode = "404", description = "One or more rules not found in the registry."),
|
||||
@ApiResponse(responseCode = "415", description = "Unsupported media type."),
|
||||
@ApiResponse(responseCode = "422", description = "Unable to serialize rule.") })
|
||||
public Response createFileFormatForRules(@Context HttpHeaders httpHeaders,
|
||||
@DefaultValue("Normal") @QueryParam("serializationOption") @Parameter(description = "Decides what to include in serialized rules") RuleSerializationOption option,
|
||||
@Parameter(description = "Array of rule UIDs. If empty or omitted, return all rules.") @Nullable List<String> ruleUIDs) {
|
||||
String acceptHeader = httpHeaders.getHeaderString(HttpHeaders.ACCEPT);
|
||||
logger.debug("createFileFormatForRules: mediaType = {}, ruleUIDs = {}", acceptHeader, ruleUIDs);
|
||||
RuleSerializer serializer = getRuleSerializer(acceptHeader);
|
||||
if (serializer == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.UNSUPPORTED_MEDIA_TYPE,
|
||||
"Unsupported media type '" + acceptHeader + "'");
|
||||
}
|
||||
List<Rule> rules;
|
||||
if (ruleUIDs == null || ruleUIDs.isEmpty()) {
|
||||
Collection<Rule> all = ruleRegistry.getAll();
|
||||
if (all instanceof List<Rule> allList) {
|
||||
rules = allList;
|
||||
} else {
|
||||
rules = new ArrayList<>(all);
|
||||
}
|
||||
} else {
|
||||
rules = new ArrayList<>();
|
||||
for (String ruleUID : ruleUIDs) {
|
||||
Rule rule = ruleRegistry.get(ruleUID);
|
||||
if (rule == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.NOT_FOUND,
|
||||
"Rule with UID '" + ruleUID + "' not found in the rule registry!");
|
||||
}
|
||||
rules.add(rule);
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
String genId = newIdForSerialization();
|
||||
try {
|
||||
serializer.setRulesToBeSerialized(genId, rules, option);
|
||||
} catch (SerializationException e) {
|
||||
return JSONResponse.createErrorResponse(UNPROCESSABLE_ENTITY, e.getMessage());
|
||||
}
|
||||
serializer.generateFormat(genId, outputStream);
|
||||
return Response.ok(outputStream.toString(StandardCharsets.UTF_8)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/rules/check")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(operationId = "canSerializeRules", summary = "Checks if the specified rule(s) can be serialized to the target format.", security = {
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SerializabilityResultsDTO.class))),
|
||||
@ApiResponse(responseCode = "400", description = "No rule specified."),
|
||||
@ApiResponse(responseCode = "404", description = "One or more rules not found in the registry."),
|
||||
@ApiResponse(responseCode = "415", description = "Unsupported media type.") })
|
||||
public Response canSerializeRules(@Context HttpHeaders httpHeaders,
|
||||
@DefaultValue("application/yaml") @QueryParam("targetFormat") @Parameter(description = "Target format", schema = @Schema(type = "string", allowableValues = {
|
||||
"application/vnd.openhab.dsl.rule", "application/yaml" })) String targetFormat,
|
||||
@RequestBody(description = "JSON rule data", content = @Content(mediaType = MediaType.APPLICATION_JSON, examples = {
|
||||
@ExampleObject(name = "Rule UID example", value = JSON_RULE_UID_CHECK_EXAMPLE),
|
||||
@ExampleObject(name = "Rule example", value = JSON_RULE_CHECK_EXAMPLE) }, schema = @Schema(oneOf = {
|
||||
StringList.class, FileFormatDTO.class }))) JsonElement data) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("createFileFormatForRules: targetFormat = {}, data = {}", targetFormat, data.getAsString());
|
||||
}
|
||||
RuleSerializer serializer = getRuleSerializer(targetFormat);
|
||||
if (serializer == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.UNSUPPORTED_MEDIA_TYPE,
|
||||
"Unsupported target format '" + targetFormat + "'");
|
||||
}
|
||||
List<Rule> rules = new ArrayList<>();
|
||||
if (data.isJsonArray()) {
|
||||
Type uidListType = new TypeToken<List<String>>() {
|
||||
}.getType();
|
||||
List<String> uids = gson.fromJson(data, uidListType);
|
||||
if (uids == null || uids.isEmpty()) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.BAD_REQUEST, "No Rule UID specified");
|
||||
}
|
||||
for (String ruleUID : uids) {
|
||||
Rule rule = ruleRegistry.get(ruleUID);
|
||||
if (rule == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.NOT_FOUND,
|
||||
"Rule with UID '" + ruleUID + "' not found in the rule registry!");
|
||||
}
|
||||
rules.add(rule);
|
||||
}
|
||||
} else if (data.isJsonObject()) {
|
||||
FileFormatDTO dto = gson.fromJson(data, FileFormatDTO.class);
|
||||
if (dto != null && dto.rules != null) {
|
||||
for (RuleDTO ruleData : dto.rules) {
|
||||
rules.add(RuleDTOMapper.map(ruleData));
|
||||
}
|
||||
}
|
||||
if (rules.isEmpty()) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.BAD_REQUEST, "No Rule specified");
|
||||
}
|
||||
} else {
|
||||
return JSONResponse.createErrorResponse(Response.Status.BAD_REQUEST, "Unsupported input");
|
||||
}
|
||||
return JSONResponse.createResponse(Response.Status.OK,
|
||||
new SerializabilityResultsDTO(serializer.checkSerializability(rules)), null);
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/ruletemplates")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces({ "application/yaml", MediaType.APPLICATION_JSON })
|
||||
@Operation(operationId = "createFileFormatForRuleTemplates", summary = "Create file format for a list of rule templates in the registry.", security = {
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = {
|
||||
@Content(mediaType = "application/yaml", schema = @Schema(example = YAML_RULE_TEMPLATE_EXAMPLE)) }),
|
||||
@ApiResponse(responseCode = "404", description = "One or more rule templates not found in the registry."),
|
||||
@ApiResponse(responseCode = "415", description = "Unsupported media type."),
|
||||
@ApiResponse(responseCode = "422", description = "Unable to serialize rule template.") })
|
||||
public Response createFileFormatForRuleTemplates(@Context HttpHeaders httpHeaders,
|
||||
@DefaultValue("Normal") @QueryParam("serializationOption") @Parameter(description = "Decides what to include in serialized rule templates") RuleTemplateSerializationOption option,
|
||||
@Parameter(description = "Array of rule template UIDs. If empty or omitted, return all rule templates.") @Nullable List<String> templateUIDs) {
|
||||
String acceptHeader = httpHeaders.getHeaderString(HttpHeaders.ACCEPT);
|
||||
logger.debug("createFileFormatForRuleTemplates: mediaType = {}, templateUIDs = {}", acceptHeader, templateUIDs);
|
||||
RuleTemplateSerializer serializer = getRuleTemplateSerializer(acceptHeader);
|
||||
if (serializer == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.UNSUPPORTED_MEDIA_TYPE,
|
||||
"Unsupported media type '" + acceptHeader + "'");
|
||||
}
|
||||
List<RuleTemplate> templates;
|
||||
if (templateUIDs == null || templateUIDs.isEmpty()) {
|
||||
Collection<RuleTemplate> all = templateRegistry.getAll();
|
||||
if (all instanceof List<RuleTemplate> allList) {
|
||||
templates = allList;
|
||||
} else {
|
||||
templates = new ArrayList<>(all);
|
||||
}
|
||||
} else {
|
||||
templates = new ArrayList<>();
|
||||
for (String templateUID : templateUIDs) {
|
||||
RuleTemplate template = templateRegistry.get(templateUID);
|
||||
if (template == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.NOT_FOUND,
|
||||
"Rule template with UID '" + templateUID + "' not found in the registry!");
|
||||
}
|
||||
templates.add(template);
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
String genId = newIdForSerialization();
|
||||
try {
|
||||
serializer.setTemplatesToBeSerialized(genId, templates, option);
|
||||
} catch (SerializationException e) {
|
||||
return JSONResponse.createErrorResponse(UNPROCESSABLE_ENTITY, e.getMessage());
|
||||
}
|
||||
serializer.generateFormat(genId, outputStream);
|
||||
return Response.ok(outputStream.toString(StandardCharsets.UTF_8)).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/sitemaps")
|
||||
@@ -633,21 +1065,24 @@ public class FileFormatResource implements RESTResource {
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/create")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ "text/vnd.openhab.dsl.thing", "text/vnd.openhab.dsl.item", "text/vnd.openhab.dsl.sitemap",
|
||||
"application/yaml" })
|
||||
@Produces({ "text/vnd.openhab.dsl.thing", "text/vnd.openhab.dsl.item", "application/vnd.openhab.dsl.rule",
|
||||
"text/vnd.openhab.dsl.sitemap", "application/yaml" })
|
||||
@Operation(operationId = "create", summary = "Create file format.", security = {
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = {
|
||||
@Content(mediaType = "application/yaml", schema = @Schema(example = YAML_FULL_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.thing", schema = @Schema(example = DSL_THINGS_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.item", schema = @Schema(example = DSL_ITEMS_EXAMPLE)),
|
||||
@Content(mediaType = "application/vnd.openhab.dsl.rule", schema = @Schema(example = DSL_RULE_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.sitemap", schema = @Schema(example = DSL_SITEMAPS_EXAMPLE)) }),
|
||||
@ApiResponse(responseCode = "400", description = "Invalid JSON data."),
|
||||
@ApiResponse(responseCode = "415", description = "Unsupported media type.") })
|
||||
@ApiResponse(responseCode = "415", description = "Unsupported media type."),
|
||||
@ApiResponse(responseCode = "422", description = "Unable to serialize entity.") })
|
||||
public Response create(final @Context HttpHeaders httpHeaders,
|
||||
@DefaultValue("false") @QueryParam("hideDefaultParameters") @Parameter(description = "if true, exclude the configuration parameters having the default value from the result.") boolean hideDefaultParameters,
|
||||
@DefaultValue("false") @QueryParam("hideDefaultChannels") @Parameter(description = "if true, exclude the non extensible channels having a default configuration from the result.") boolean hideDefaultChannels,
|
||||
@DefaultValue("false") @QueryParam("hideChannelLinksAndMetadata") @Parameter(description = "if true, exclude the channel links and metadata for items from the result.") boolean hideChannelLinksAndMetadata,
|
||||
@DefaultValue("Normal") @QueryParam("ruleSerializationOption") @Parameter(description = "Decides what to include in serialized rules and rule templates") RuleSerializationOption ruleOption,
|
||||
@RequestBody(description = "JSON data", required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = FileFormatDTO.class))) FileFormatDTO data) {
|
||||
String acceptHeader = httpHeaders.getHeaderString(HttpHeaders.ACCEPT);
|
||||
logger.debug("create: mediaType = {}", acceptHeader);
|
||||
@@ -657,15 +1092,20 @@ public class FileFormatResource implements RESTResource {
|
||||
List<Item> items = new ArrayList<>();
|
||||
List<Metadata> metadata = new ArrayList<>();
|
||||
Map<String, String> stateFormatters = new HashMap<>();
|
||||
List<Rule> rules = new ArrayList<>();
|
||||
List<RuleTemplate> templates = new ArrayList<>();
|
||||
List<Sitemap> sitemaps = new ArrayList<>();
|
||||
List<String> errors = new ArrayList<>();
|
||||
if (!convertFromFileFormatDTO(data, tags, things, items, metadata, stateFormatters, sitemaps, errors)) {
|
||||
if (!convertFromFileFormatDTO(data, tags, things, items, metadata, stateFormatters, rules, templates, sitemaps,
|
||||
errors)) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(String.join("\n", errors)).build();
|
||||
}
|
||||
|
||||
SemanticTagSerializer tagSerializer = getSemanticTagSerializer(acceptHeader);
|
||||
ThingSerializer thingSerializer = getThingSerializer(acceptHeader);
|
||||
ItemSerializer itemSerializer = getItemSerializer(acceptHeader);
|
||||
RuleSerializer ruleSerializer = getRuleSerializer(acceptHeader);
|
||||
RuleTemplateSerializer templateSerializer = getRuleTemplateSerializer(acceptHeader);
|
||||
SitemapSerializer sitemapSerializer = getSitemapSerializer(acceptHeader);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
String genId = newIdForSerialization();
|
||||
@@ -691,6 +1131,21 @@ public class FileFormatResource implements RESTResource {
|
||||
stateFormatters, hideDefaultParameters);
|
||||
itemSerializer.generateFormat(genId, outputStream);
|
||||
break;
|
||||
case "application/vnd.openhab.dsl.rule":
|
||||
if (ruleSerializer == null) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.UNSUPPORTED_MEDIA_TYPE,
|
||||
"Unsupported media type '" + acceptHeader + "'");
|
||||
}
|
||||
if (rules.isEmpty()) {
|
||||
return JSONResponse.createErrorResponse(Response.Status.BAD_REQUEST, "No rule loaded from input");
|
||||
}
|
||||
try {
|
||||
ruleSerializer.setRulesToBeSerialized(genId, rules, ruleOption);
|
||||
} catch (SerializationException e) {
|
||||
return JSONResponse.createErrorResponse(UNPROCESSABLE_ENTITY, e.getMessage());
|
||||
}
|
||||
ruleSerializer.generateFormat(genId, outputStream);
|
||||
break;
|
||||
case "text/vnd.openhab.dsl.sitemap":
|
||||
if (sitemapSerializer == null) {
|
||||
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
|
||||
@@ -712,6 +1167,23 @@ public class FileFormatResource implements RESTResource {
|
||||
itemSerializer.setItemsToBeSerialized(genId, items,
|
||||
hideChannelLinksAndMetadata ? List.of() : metadata, stateFormatters, hideDefaultParameters);
|
||||
}
|
||||
if (ruleSerializer != null) {
|
||||
try {
|
||||
ruleSerializer.setRulesToBeSerialized(genId, rules, ruleOption);
|
||||
} catch (SerializationException e) {
|
||||
return JSONResponse.createErrorResponse(UNPROCESSABLE_ENTITY, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (templateSerializer != null) {
|
||||
try {
|
||||
templateSerializer.setTemplatesToBeSerialized(genId, templates,
|
||||
ruleOption == RuleSerializationOption.INCLUDE_ALL
|
||||
? RuleTemplateSerializationOption.INCLUDE_ALL
|
||||
: RuleTemplateSerializationOption.NORMAL);
|
||||
} catch (SerializationException e) {
|
||||
return JSONResponse.createErrorResponse(UNPROCESSABLE_ENTITY, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (sitemapSerializer != null) {
|
||||
sitemapSerializer.setSitemapsToBeSerialized(genId, sitemaps);
|
||||
}
|
||||
@@ -721,6 +1193,10 @@ public class FileFormatResource implements RESTResource {
|
||||
thingSerializer.generateFormat(genId, outputStream);
|
||||
} else if (itemSerializer != null) {
|
||||
itemSerializer.generateFormat(genId, outputStream);
|
||||
} else if (ruleSerializer != null) {
|
||||
ruleSerializer.generateFormat(genId, outputStream);
|
||||
} else if (templateSerializer != null) {
|
||||
templateSerializer.generateFormat(genId, outputStream);
|
||||
} else if (sitemapSerializer != null) {
|
||||
sitemapSerializer.generateFormat(genId, outputStream);
|
||||
}
|
||||
@@ -735,8 +1211,8 @@ public class FileFormatResource implements RESTResource {
|
||||
@POST
|
||||
@RolesAllowed({ Role.ADMIN })
|
||||
@Path("/parse")
|
||||
@Consumes({ "text/vnd.openhab.dsl.thing", "text/vnd.openhab.dsl.item", "text/vnd.openhab.dsl.sitemap",
|
||||
"application/yaml" })
|
||||
@Consumes({ "text/vnd.openhab.dsl.thing", "text/vnd.openhab.dsl.item", "application/vnd.openhab.dsl.rule",
|
||||
"text/vnd.openhab.dsl.sitemap", "application/yaml" })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "parse", summary = "Parse file format.", security = {
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = {
|
||||
@@ -748,6 +1224,7 @@ public class FileFormatResource implements RESTResource {
|
||||
@Content(mediaType = "application/yaml", schema = @Schema(example = YAML_FULL_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.thing", schema = @Schema(example = DSL_THINGS_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.item", schema = @Schema(example = DSL_ITEMS_EXAMPLE)),
|
||||
@Content(mediaType = "application/vnd.openhab.dsl.rule", schema = @Schema(example = DSL_RULE_EXAMPLE)),
|
||||
@Content(mediaType = "text/vnd.openhab.dsl.sitemap", schema = @Schema(example = DSL_SITEMAPS_EXAMPLE)) }) String input) {
|
||||
String contentTypeHeader = httpHeaders.getHeaderString(HttpHeaders.CONTENT_TYPE);
|
||||
logger.debug("parse: contentType = {}", contentTypeHeader);
|
||||
@@ -760,11 +1237,15 @@ public class FileFormatResource implements RESTResource {
|
||||
Collection<Metadata> metadata = List.of();
|
||||
Collection<ItemChannelLink> channelLinks = List.of();
|
||||
Map<String, String> stateFormatters = Map.of();
|
||||
Collection<Rule> rules = List.of();
|
||||
Collection<RuleTemplate> templates = List.of();
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
SemanticTagParser tagParser = getSemanticTagParser(contentTypeHeader);
|
||||
ThingParser thingParser = getThingParser(contentTypeHeader);
|
||||
ItemParser itemParser = getItemParser(contentTypeHeader);
|
||||
RuleParser ruleParser = getRuleParser(contentTypeHeader);
|
||||
RuleTemplateParser templateParser = getRuleTemplateParser(contentTypeHeader);
|
||||
SitemapParser sitemapParser = getSitemapParser(contentTypeHeader);
|
||||
ObjectParser<?> parserStartingParsing = null;
|
||||
String modelName = null;
|
||||
@@ -808,6 +1289,22 @@ public class FileFormatResource implements RESTResource {
|
||||
channelLinks = thingParser.getParsedChannelLinks(modelName);
|
||||
}
|
||||
break;
|
||||
case "application/vnd.openhab.dsl.rule":
|
||||
if (ruleParser == null) {
|
||||
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
|
||||
.entity("Unsupported content type '" + contentTypeHeader + "'!").build();
|
||||
}
|
||||
parserStartingParsing = ruleParser;
|
||||
modelName = ruleParser.startParsingFormat(input, errors, warnings);
|
||||
if (modelName == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(String.join("\n", errors)).build();
|
||||
}
|
||||
rules = ruleParser.getParsedObjects(modelName);
|
||||
if (rules.isEmpty()) {
|
||||
ruleParser.finishParsingFormat(modelName);
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity("No rule loaded from input").build();
|
||||
}
|
||||
break;
|
||||
case "text/vnd.openhab.dsl.sitemap":
|
||||
if (sitemapParser == null) {
|
||||
return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
|
||||
@@ -835,7 +1332,7 @@ public class FileFormatResource implements RESTResource {
|
||||
channelLinks = thingParser.getParsedChannelLinks(modelName);
|
||||
}
|
||||
if (itemParser != null) {
|
||||
// Avoid parsing the input a second time
|
||||
// Avoid parsing the input again
|
||||
if (modelName == null) {
|
||||
parserStartingParsing = itemParser;
|
||||
modelName = itemParser.startParsingFormat(input, errors, warnings);
|
||||
@@ -848,8 +1345,32 @@ public class FileFormatResource implements RESTResource {
|
||||
metadata = itemParser.getParsedMetadata(modelName);
|
||||
stateFormatters = itemParser.getParsedStateFormatters(modelName);
|
||||
}
|
||||
if (ruleParser != null) {
|
||||
// Avoid parsing the input again
|
||||
if (modelName == null) {
|
||||
parserStartingParsing = ruleParser;
|
||||
modelName = ruleParser.startParsingFormat(input, errors, warnings);
|
||||
if (modelName == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(String.join("\n", errors))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
rules = ruleParser.getParsedObjects(modelName);
|
||||
}
|
||||
if (templateParser != null) {
|
||||
// Avoid parsing the input again
|
||||
if (modelName == null) {
|
||||
parserStartingParsing = templateParser;
|
||||
modelName = templateParser.startParsingFormat(input, errors, warnings);
|
||||
if (modelName == null) {
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(String.join("\n", errors))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
templates = templateParser.getParsedObjects(modelName);
|
||||
}
|
||||
if (sitemapParser != null) {
|
||||
// Avoid parsing the input a second time
|
||||
// Avoid parsing the input again
|
||||
if (modelName == null) {
|
||||
parserStartingParsing = sitemapParser;
|
||||
modelName = sitemapParser.startParsingFormat(input, errors, warnings);
|
||||
@@ -861,7 +1382,7 @@ public class FileFormatResource implements RESTResource {
|
||||
sitemaps = sitemapParser.getParsedObjects(modelName);
|
||||
}
|
||||
if (tagParser != null) {
|
||||
// Avoid parsing the input a second time
|
||||
// Avoid parsing the input again
|
||||
if (modelName == null) {
|
||||
parserStartingParsing = tagParser;
|
||||
modelName = tagParser.startParsingFormat(input, errors, warnings);
|
||||
@@ -878,7 +1399,7 @@ public class FileFormatResource implements RESTResource {
|
||||
.entity("Unsupported content type '" + contentTypeHeader + "'!").build();
|
||||
}
|
||||
ExtendedFileFormatDTO result = convertToFileFormatDTO(tags, things, items, metadata, stateFormatters,
|
||||
channelLinks, sitemaps, warnings);
|
||||
channelLinks, rules, templates, sitemaps, warnings);
|
||||
if (modelName != null && parserStartingParsing != null) {
|
||||
parserStartingParsing.finishParsingFormat(modelName);
|
||||
}
|
||||
@@ -886,7 +1407,7 @@ public class FileFormatResource implements RESTResource {
|
||||
}
|
||||
|
||||
private String newIdForSerialization() {
|
||||
return GEN_ID_PATTERN.formatted(++counter);
|
||||
return String.format(Locale.ROOT, GEN_ID_PATTERN, counter.incrementAndGet());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1056,6 +1577,26 @@ public class FileFormatResource implements RESTResource {
|
||||
};
|
||||
}
|
||||
|
||||
private @Nullable RuleSerializer getRuleSerializer(String mediaType) {
|
||||
switch (mediaType) {
|
||||
case "application/yaml":
|
||||
return ruleSerializers.get("YAML");
|
||||
case "application/vnd.openhab.dsl.rule":
|
||||
return ruleSerializers.get("DSL");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable RuleTemplateSerializer getRuleTemplateSerializer(String mediaType) {
|
||||
switch (mediaType) {
|
||||
case "application/yaml":
|
||||
return templateSerializers.get("YAML");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable SitemapSerializer getSitemapSerializer(String mediaType) {
|
||||
return switch (mediaType) {
|
||||
case "text/vnd.openhab.dsl.sitemap" -> sitemapSerializers.get("DSL");
|
||||
@@ -1088,6 +1629,26 @@ public class FileFormatResource implements RESTResource {
|
||||
};
|
||||
}
|
||||
|
||||
private @Nullable RuleParser getRuleParser(String contentType) {
|
||||
switch (contentType) {
|
||||
case "application/yaml":
|
||||
return ruleParsers.get("YAML");
|
||||
case "application/vnd.openhab.dsl.rule":
|
||||
return ruleParsers.get("DSL");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable RuleTemplateParser getRuleTemplateParser(String contentType) {
|
||||
switch (contentType) {
|
||||
case "application/yaml":
|
||||
return templateParsers.get("YAML");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable SitemapParser getSitemapParser(String contentType) {
|
||||
return switch (contentType) {
|
||||
case "text/vnd.openhab.dsl.sitemap" -> sitemapParsers.get("DSL");
|
||||
@@ -1118,8 +1679,8 @@ public class FileFormatResource implements RESTResource {
|
||||
}
|
||||
|
||||
private boolean convertFromFileFormatDTO(FileFormatDTO data, List<SemanticTag> tags, List<Thing> things,
|
||||
List<Item> items, List<Metadata> metadata, Map<String, String> stateFormatters, List<Sitemap> sitemaps,
|
||||
List<String> errors) {
|
||||
List<Item> items, List<Metadata> metadata, Map<String, String> stateFormatters, List<Rule> rules,
|
||||
List<RuleTemplate> templates, List<Sitemap> sitemaps, List<String> errors) {
|
||||
boolean ok = true;
|
||||
if (data.things != null) {
|
||||
for (ThingDTO thingData : data.things) {
|
||||
@@ -1202,6 +1763,16 @@ public class FileFormatResource implements RESTResource {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.rules != null) {
|
||||
for (RuleDTO ruleData : data.rules) {
|
||||
rules.add(RuleDTOMapper.map(ruleData));
|
||||
}
|
||||
}
|
||||
if (data.ruleTemplates != null) {
|
||||
for (RuleTemplateDTO templateData : data.ruleTemplates) {
|
||||
templates.add(RuleTemplateDTOMapper.map(templateData));
|
||||
}
|
||||
}
|
||||
if (data.sitemaps != null) {
|
||||
for (SitemapDefinitionDTO sitemapData : data.sitemaps) {
|
||||
String name = sitemapData.name;
|
||||
@@ -1239,7 +1810,8 @@ public class FileFormatResource implements RESTResource {
|
||||
|
||||
private ExtendedFileFormatDTO convertToFileFormatDTO(Collection<SemanticTag> tags, Collection<Thing> things,
|
||||
Collection<Item> items, Collection<Metadata> metadata, Map<String, String> stateFormatters,
|
||||
Collection<ItemChannelLink> channelLinks, Collection<Sitemap> sitemaps, List<String> warnings) {
|
||||
Collection<ItemChannelLink> channelLinks, Collection<Rule> rules, Collection<RuleTemplate> templates,
|
||||
Collection<Sitemap> sitemaps, List<String> warnings) {
|
||||
ExtendedFileFormatDTO dto = new ExtendedFileFormatDTO();
|
||||
dto.warnings = warnings.isEmpty() ? null : warnings;
|
||||
if (!things.isEmpty()) {
|
||||
@@ -1286,6 +1858,18 @@ public class FileFormatResource implements RESTResource {
|
||||
FileFormatItemDTOMapper.map(item, metadata, stateFormatters.get(item.getName()), channelLinks));
|
||||
});
|
||||
}
|
||||
if (!rules.isEmpty()) {
|
||||
dto.rules = new ArrayList<>();
|
||||
for (Rule rule : rules) {
|
||||
dto.rules.add(RuleDTOMapper.map(rule));
|
||||
}
|
||||
}
|
||||
if (!templates.isEmpty()) {
|
||||
dto.ruleTemplates = new ArrayList<>();
|
||||
for (RuleTemplate template : templates) {
|
||||
dto.ruleTemplates.add(RuleTemplateDTOMapper.map(template));
|
||||
}
|
||||
}
|
||||
if (!sitemaps.isEmpty()) {
|
||||
dto.sitemaps = new ArrayList<>();
|
||||
sitemaps.forEach(sitemap -> {
|
||||
@@ -1400,4 +1984,7 @@ public class FileFormatResource implements RESTResource {
|
||||
throw new BadRequestException("Invalid URI syntax: " + uriString);
|
||||
}
|
||||
}
|
||||
|
||||
private static interface StringList extends List<String> {
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.rest.core.internal.rule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* A DTO representing a list of {@link SerializabilityResult}s.
|
||||
*
|
||||
* @author Ravi Nadahar - initial contribution
|
||||
*/
|
||||
@Schema(name = "SerializabilityResults")
|
||||
public class SerializabilityResultsDTO {
|
||||
public List<SerializabilityResult<String>> results;
|
||||
|
||||
public SerializabilityResultsDTO(@Nullable List<SerializabilityResult<String>> results) {
|
||||
this.results = results;
|
||||
}
|
||||
}
|
||||
+44
-2
@@ -28,11 +28,14 @@ import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.nodemodel.ICompositeNode;
|
||||
import org.eclipse.xtext.nodemodel.ILeafNode;
|
||||
import org.eclipse.xtext.nodemodel.INode;
|
||||
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
|
||||
import org.eclipse.xtext.xbase.XExpression;
|
||||
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.Module;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.RuleProvider;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
@@ -88,6 +91,7 @@ import org.openhab.core.model.rule.rules.TimeOfDayCondition;
|
||||
import org.openhab.core.model.rule.rules.TimerTrigger;
|
||||
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.WeekdayCondition;
|
||||
import org.openhab.core.model.rule.rules.impl.RuleImpl;
|
||||
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
|
||||
import org.openhab.core.model.script.script.Script;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
@@ -114,7 +118,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class DSLRuleProvider
|
||||
implements RuleProvider, ModelRepositoryChangeListener, DSLScriptContextProvider, ReadyTracker {
|
||||
|
||||
static final String MIMETYPE_OPENHAB_DSL_RULE = "application/vnd.openhab.dsl.rule";
|
||||
public static final String MIMETYPE_OPENHAB_DSL_RULE = "application/vnd.openhab.dsl.rule";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DSLRuleProvider.class);
|
||||
private final Collection<ProviderChangeListener<Rule>> listeners = new ArrayList<>();
|
||||
@@ -357,11 +361,49 @@ public class DSLRuleProvider
|
||||
Configuration cfg = new Configuration();
|
||||
cfg.put(AbstractScriptModuleHandler.CONFIG_SCRIPT, context + removeIndentation(script));
|
||||
cfg.put(AbstractScriptModuleHandler.CONFIG_SCRIPT_TYPE, MIMETYPE_OPENHAB_DSL_RULE);
|
||||
INode ruleNode = NodeModelUtils.findActualNodeFor(rule);
|
||||
boolean sharedContext = hasSharedContext(ruleNode);
|
||||
if (sharedContext) {
|
||||
cfg.put(Module.SHARED_CONTEXT, Boolean.TRUE);
|
||||
}
|
||||
List<Action> actions = List.of(ActionBuilder.create().withId("script").withTypeUID(ScriptActionHandler.TYPE_ID)
|
||||
.withConfiguration(cfg).build());
|
||||
|
||||
Configuration ruleCfg = new Configuration();
|
||||
String source = sharedContext ? ruleNode.getParent().getText() : ruleNode.getText().replaceFirst("^\\R+", "");
|
||||
source = source.replaceAll("\\R", "\n");
|
||||
if (!source.endsWith("\n")) {
|
||||
source += '\n';
|
||||
}
|
||||
ruleCfg.put(Rule.SOURCE, source);
|
||||
ruleCfg.put(Rule.SOURCE_TYPE, MIMETYPE_OPENHAB_DSL_RULE);
|
||||
return RuleBuilder.create(uid).withTags(rule.getTags()).withName(name).withTriggers(triggers)
|
||||
.withActions(actions).withConditions(conditions).build();
|
||||
.withActions(actions).withConditions(conditions).withConfiguration(ruleCfg).build();
|
||||
}
|
||||
|
||||
private boolean hasSharedContext(INode ruleNode) {
|
||||
INode node = ruleNode;
|
||||
EObject eObject;
|
||||
while ((node = node.getPreviousSibling()) != null) {
|
||||
if (node instanceof ILeafNode leaf && leaf.isHidden()) {
|
||||
continue;
|
||||
}
|
||||
if ((eObject = node.getSemanticElement()) != null && !(eObject instanceof RuleImpl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
node = ruleNode;
|
||||
while ((node = node.getNextSibling()) != null) {
|
||||
if (node instanceof ILeafNode leaf && leaf.isHidden()) {
|
||||
continue;
|
||||
}
|
||||
if ((eObject = node.getSemanticElement()) != null && !(eObject instanceof RuleImpl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String removeIndentation(String script) {
|
||||
|
||||
+854
@@ -0,0 +1,854 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.rule.runtime.internal.converter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.emf.common.util.EList;
|
||||
import org.eclipse.emf.ecore.util.Diagnostician;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.xbase.XBlockExpression;
|
||||
import org.eclipse.xtext.xbase.XStringLiteral;
|
||||
import org.eclipse.xtext.xbase.XVariableDeclaration;
|
||||
import org.eclipse.xtext.xbase.XbaseFactory;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.Module;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.converter.RuleParser;
|
||||
import org.openhab.core.automation.converter.RuleSerializer;
|
||||
import org.openhab.core.automation.internal.module.handler.ChannelEventTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.DateTimeTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.DayOfWeekConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.EphemerisConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupCommandTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupStateTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.IntervalConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ItemCommandTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ItemStateConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ThingStatusConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.ThingStatusTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.TimeOfDayConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.TimeOfDayTriggerHandler;
|
||||
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
|
||||
import org.openhab.core.automation.util.ActionBuilder;
|
||||
import org.openhab.core.automation.util.RuleBuilder;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
import org.openhab.core.model.core.ModelRepository;
|
||||
import org.openhab.core.model.rule.rules.ChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.CommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.DateTimeTrigger;
|
||||
import org.openhab.core.model.rule.rules.DayOfWeekCondition;
|
||||
import org.openhab.core.model.rule.rules.EventEmittedTrigger;
|
||||
import org.openhab.core.model.rule.rules.EventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberCommandEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.GroupMemberUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.HolidayCondition;
|
||||
import org.openhab.core.model.rule.rules.InDaysetCondition;
|
||||
import org.openhab.core.model.rule.rules.IntervalCondition;
|
||||
import org.openhab.core.model.rule.rules.ItemStateCondition;
|
||||
import org.openhab.core.model.rule.rules.RuleModel;
|
||||
import org.openhab.core.model.rule.rules.RulesFactory;
|
||||
import org.openhab.core.model.rule.rules.SystemStartlevelTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateChangedEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStateUpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.ThingStatusCondition;
|
||||
import org.openhab.core.model.rule.rules.TimeOfDayCondition;
|
||||
import org.openhab.core.model.rule.rules.TimerTrigger;
|
||||
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
|
||||
import org.openhab.core.model.rule.rules.ValidCommand;
|
||||
import org.openhab.core.model.rule.rules.ValidState;
|
||||
import org.openhab.core.model.rule.rules.ValidTrigger;
|
||||
import org.openhab.core.model.rule.rules.WeekdayCondition;
|
||||
import org.openhab.core.model.rule.runtime.internal.DSLRuleProvider;
|
||||
import org.openhab.core.model.script.scoping.StateAndCommandProvider;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link DslRuleFileConverter} is the DSL converter for {@link Rule} objects, which can parse and generate Rule DSL
|
||||
* syntax.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { RuleSerializer.class, RuleParser.class })
|
||||
public class DslRuleConverter implements RuleSerializer, RuleParser {
|
||||
|
||||
private static final String SCRIPT_PLACEHOLDER_PREFIX = "SCRIPT_PLACEHOLDER_";
|
||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(
|
||||
"(?<=then\\R)^\\s*val\\splaceholder=\"SCRIPT_PLACEHOLDER_(?<uid>[^\"]+)\"\\s*$\\R*", Pattern.MULTILINE);
|
||||
private static final Pattern CONTEXT_COMMENT_PATTERN = Pattern.compile("^// context:.*$\\R", Pattern.MULTILINE);
|
||||
private static final Pattern INDENTATION_PATTERN = Pattern.compile("^(?=.)", Pattern.MULTILINE);
|
||||
private static final Pattern NUMERIC_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
|
||||
private static final Pattern TIME_PATTERN = Pattern.compile("^([012]?\\d):([0-5]\\d)$");
|
||||
private static final Pattern INDEX_PATTERN = Pattern.compile("-(?<idx>\\d+)$");
|
||||
private final Set<String> enumStates;
|
||||
private final Set<String> enumCommands;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DslRuleConverter.class);
|
||||
|
||||
private final ModelRepository modelRepository;
|
||||
private final DSLRuleProvider ruleProvider;
|
||||
|
||||
private record ScriptElement(String placeholderLiteral, String scriptContent) {
|
||||
}
|
||||
|
||||
private final Map<String, RuleModel> elementsToGenerate = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, List<ScriptElement>> scriptElements = new ConcurrentHashMap<>();
|
||||
|
||||
@Activate
|
||||
public DslRuleConverter(@Reference ModelRepository modelRepository, @Reference DSLRuleProvider ruleProvider) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.ruleProvider = ruleProvider;
|
||||
|
||||
Set<String> enums = new LinkedHashSet<>();
|
||||
for (State state : StateAndCommandProvider.getAllStates()) {
|
||||
enums.add(state.toString());
|
||||
}
|
||||
this.enumStates = Set.copyOf(enums);
|
||||
|
||||
enums = new LinkedHashSet<>();
|
||||
for (Command command : StateAndCommandProvider.getAllCommands()) {
|
||||
enums.add(command.toString());
|
||||
}
|
||||
this.enumCommands = Set.copyOf(enums);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParserFormat() {
|
||||
return "DSL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGeneratedFormat() {
|
||||
return "DSL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SerializabilityResult<String>> checkSerializability(Collection<Rule> rules) {
|
||||
List<SerializabilityResult<String>> result = new ArrayList<>(rules.size());
|
||||
List<String> errors = new ArrayList<>();
|
||||
String s;
|
||||
for (Rule rule : rules) {
|
||||
if (rule instanceof SimpleRule) {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), false,
|
||||
"Rule '" + rule.getUID() + "' is a SimpleRule with an inaccessible action."));
|
||||
continue;
|
||||
}
|
||||
errors.clear();
|
||||
if (rule.getVisibility() != Visibility.VISIBLE) {
|
||||
errors.add("isn't visible");
|
||||
}
|
||||
if ((s = rule.getDescription()) != null && !s.isBlank()) {
|
||||
errors.add("has a description");
|
||||
}
|
||||
for (Trigger trigger : rule.getTriggers()) {
|
||||
try {
|
||||
buildModelTrigger(trigger);
|
||||
} catch (SerializationException e) {
|
||||
errors.add("trigger '" + trigger.getId() + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
for (Condition condition : rule.getConditions()) {
|
||||
if (!condition.getInputs().isEmpty()) {
|
||||
errors.add("condition '" + condition.getId() + "' has mapped inputs");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
buildModelCondition(condition);
|
||||
} catch (SerializationException e) {
|
||||
errors.add("condition '" + condition.getId() + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.getActions().size() != 1) {
|
||||
errors.add("has " + rule.getActions().size() + " actions but exactly 1 is required");
|
||||
} else {
|
||||
Action action = rule.getActions().getFirst();
|
||||
if (!action.getInputs().isEmpty()) {
|
||||
errors.add("action '" + action.getId() + "' has mapped inputs");
|
||||
} else if (action.getConfiguration().get("type") instanceof String type) {
|
||||
if (DSLRuleProvider.MIMETYPE_OPENHAB_DSL_RULE.equals(type)) {
|
||||
if (!(action.getConfiguration().get("script") instanceof String)) {
|
||||
errors.add("has no action script");
|
||||
}
|
||||
if (action.getConfiguration().get(Module.SHARED_CONTEXT) instanceof Boolean shared
|
||||
&& shared.booleanValue()) {
|
||||
errors.add("action '" + action.getId() + "' has shared context");
|
||||
}
|
||||
} else {
|
||||
errors.add("doesn't have a scripted DSL action");
|
||||
}
|
||||
} else {
|
||||
errors.add("doesn't have a scripted action");
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), true, ""));
|
||||
} else {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), false,
|
||||
"Rule '" + rule.getUID() + "': " + String.join(", ", errors) + '.'));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRulesToBeSerialized(String modelName, List<Rule> rules, RuleSerializationOption option)
|
||||
throws SerializationException {
|
||||
if (rules.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (option != RuleSerializationOption.NORMAL) {
|
||||
throw new SerializationException("DSL rules don't support serialization option '" + option + '\'');
|
||||
}
|
||||
List<String> errors = null;
|
||||
List<SerializabilityResult<String>> checks = checkSerializability(rules);
|
||||
for (SerializabilityResult<String> check : checks) {
|
||||
if (!check.ok()) {
|
||||
if (errors == null) {
|
||||
errors = new ArrayList<>();
|
||||
}
|
||||
errors.add(check.failureReason());
|
||||
}
|
||||
}
|
||||
if (errors != null) {
|
||||
throw new SerializationException(
|
||||
"Rule serialization attempt failed with:\n " + String.join("\n ", errors));
|
||||
}
|
||||
|
||||
RuleModel model = RulesFactory.eINSTANCE.createRuleModel();
|
||||
|
||||
// Ensure that the variable collection is not null, calling get() creates an empty collection.
|
||||
model.getVariables();
|
||||
|
||||
Set<Rule> handledRules = new HashSet<>();
|
||||
for (Rule rule : rules) {
|
||||
if (handledRules.contains(rule)) {
|
||||
continue;
|
||||
}
|
||||
org.openhab.core.model.rule.rules.Rule modelRule = RulesFactory.eINSTANCE.createRule();
|
||||
model.getRules().add(modelRule);
|
||||
try {
|
||||
String placeholderUid = UUID.randomUUID().toString();
|
||||
String placeholderLiteral = SCRIPT_PLACEHOLDER_PREFIX + placeholderUid;
|
||||
buildModelRule(rule, modelRule, placeholderLiteral, handledRules);
|
||||
scriptElements.compute(modelName, (k, v) -> {
|
||||
List<ScriptElement> r = v == null ? new ArrayList<>() : v;
|
||||
if (rule.getActions().getFirst().getConfiguration().get("script") instanceof String script) {
|
||||
r.add(new ScriptElement(placeholderUid, script));
|
||||
} else {
|
||||
r.add(new ScriptElement(placeholderUid, ""));
|
||||
}
|
||||
return r;
|
||||
});
|
||||
} catch (SerializationException e) {
|
||||
logger.warn("Failed to serialize rule '{}': {}", rule.getUID(), e.getMessage());
|
||||
throw new SerializationException("Rule '" + rule.getUID() + "': " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
elementsToGenerate.put(modelName, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateFormat(String modelName, OutputStream out) {
|
||||
RuleModel model = elementsToGenerate.remove(modelName);
|
||||
if (model != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
org.eclipse.emf.common.util.Diagnostic diagnostic = Diagnostician.INSTANCE.validate(model);
|
||||
if (diagnostic.getSeverity() != org.eclipse.emf.common.util.Diagnostic.OK) {
|
||||
for (org.eclipse.emf.common.util.Diagnostic child : diagnostic.getChildren()) {
|
||||
logger.debug("Model Validation Error: {}", child.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the placeholder with the actual script content
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
modelRepository.generateFileFormat(outputStream, "rules", model);
|
||||
String generated = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
|
||||
Matcher m = PLACEHOLDER_PATTERN.matcher(generated);
|
||||
int safetyValve = 0;
|
||||
List<ScriptElement> elements;
|
||||
ScriptElement element;
|
||||
String scriptContent;
|
||||
while (m.find() && safetyValve < 1000) {
|
||||
elements = scriptElements.get(modelName);
|
||||
String uid = m.group("uid");
|
||||
if (uid != null && elements != null) {
|
||||
element = elements.stream().filter(e -> uid.equals(e.placeholderLiteral)).findAny().orElse(null);
|
||||
if (element != null) {
|
||||
scriptContent = CONTEXT_COMMENT_PATTERN.matcher(element.scriptContent).replaceFirst("");
|
||||
scriptContent = INDENTATION_PATTERN.matcher(scriptContent).replaceAll("\t");
|
||||
if (!scriptContent.endsWith("\n")) {
|
||||
scriptContent += '\n';
|
||||
}
|
||||
generated = m.replaceFirst(scriptContent);
|
||||
}
|
||||
}
|
||||
m = PLACEHOLDER_PATTERN.matcher(generated);
|
||||
safetyValve++;
|
||||
}
|
||||
if (safetyValve >= 1000) {
|
||||
logger.warn(
|
||||
"Aborted replacing placeholders with script content to avoid endless loop, generated Rule DSL for '{}' will be invalid",
|
||||
modelName);
|
||||
}
|
||||
|
||||
try {
|
||||
out.write(generated.getBytes());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Exception when writing the generated syntax {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String startParsingFormat(String syntax, List<String> errors, List<String> warnings) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(syntax.getBytes(StandardCharsets.UTF_8));
|
||||
String result = modelRepository.createIsolatedModel("rules", inputStream, errors, warnings);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Rule> getParsedObjects(String modelName) {
|
||||
List<Rule> result = new ArrayList<>();
|
||||
RuleBuilder builder;
|
||||
List<Action> actions = new ArrayList<>();
|
||||
ActionBuilder aBuilder;
|
||||
LinkedHashMap<String, @Nullable Object> props;
|
||||
Set<String> usedUids = new HashSet<>();
|
||||
String fileNamePrefix = modelName.replace(".rules", "") + '-';
|
||||
String uid;
|
||||
for (Rule rule : ruleProvider.getAllFromModel(modelName)) {
|
||||
if ((uid = rule.getUID()).startsWith(fileNamePrefix) || usedUids.contains(uid)) {
|
||||
builder = RuleBuilder.create(generateUid(rule, usedUids), rule);
|
||||
} else {
|
||||
usedUids.add(uid);
|
||||
builder = RuleBuilder.create(uid, rule);
|
||||
}
|
||||
actions.clear();
|
||||
for (Action action : rule.getActions()) {
|
||||
aBuilder = ActionBuilder.create(action);
|
||||
props = new LinkedHashMap<>(action.getConfiguration().getProperties());
|
||||
if (props.get("script") instanceof String script) {
|
||||
props.put("script", CONTEXT_COMMENT_PATTERN.matcher(script).replaceFirst(""));
|
||||
}
|
||||
aBuilder.withConfiguration(new Configuration(props));
|
||||
actions.add(aBuilder.build());
|
||||
}
|
||||
builder.withActions(actions);
|
||||
result.add(builder.build());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String generateUid(Rule rule, Set<String> usedUids) {
|
||||
String result = rule.getName();
|
||||
if (result == null || result.isBlank()) {
|
||||
result = "generated-1";
|
||||
} else {
|
||||
result = result.trim().toLowerCase(Locale.ROOT).replaceAll("\\s+", "-");
|
||||
}
|
||||
|
||||
Matcher matcher;
|
||||
while (usedUids.contains(result)) {
|
||||
matcher = INDEX_PATTERN.matcher(result);
|
||||
if (matcher.find()) {
|
||||
result = matcher.replaceFirst("-" + Integer.parseInt(matcher.group("idx")) + 1);
|
||||
} else {
|
||||
result += "-2";
|
||||
}
|
||||
}
|
||||
|
||||
usedUids.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishParsingFormat(String modelName) {
|
||||
modelRepository.removeModel(modelName);
|
||||
scriptElements.remove(modelName);
|
||||
}
|
||||
|
||||
private org.openhab.core.model.rule.rules.Rule buildModelRule(Rule rule,
|
||||
org.openhab.core.model.rule.rules.Rule model, String placeholderLiteral, Set<Rule> handledRules)
|
||||
throws SerializationException {
|
||||
model.setUid(rule.getUID());
|
||||
model.setName(rule.getName());
|
||||
EList<String> tags = model.getTags();
|
||||
for (String tag : rule.getTags()) {
|
||||
tags.add(tag);
|
||||
}
|
||||
|
||||
for (Condition condition : rule.getConditions()) {
|
||||
model.getConditions().add(buildModelCondition(condition));
|
||||
}
|
||||
|
||||
for (Trigger trigger : rule.getTriggers()) {
|
||||
model.getEventtrigger().add(buildModelTrigger(trigger));
|
||||
}
|
||||
|
||||
model.setScript(createPlaceholder(placeholderLiteral));
|
||||
|
||||
handledRules.add(rule);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private EventTrigger buildModelTrigger(Trigger trigger) throws SerializationException {
|
||||
String type = trigger.getTypeUID();
|
||||
Object value;
|
||||
RulesFactory factory = RulesFactory.eINSTANCE;
|
||||
switch (type) {
|
||||
case SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(SystemTriggerHandler.CFG_STARTLEVEL);
|
||||
if (value instanceof Number num) {
|
||||
int level = num.intValue();
|
||||
if (level == 40) {
|
||||
return factory.createSystemOnStartupTrigger();
|
||||
}
|
||||
SystemStartlevelTrigger result = factory.createSystemStartlevelTrigger();
|
||||
result.setLevel(level);
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ItemCommandTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ItemCommandTriggerHandler.CFG_ITEMNAME);
|
||||
if (value instanceof String str) {
|
||||
CommandEventTrigger result = factory.createCommandEventTrigger();
|
||||
result.setItem(str);
|
||||
value = trigger.getConfiguration().get(ItemCommandTriggerHandler.CFG_COMMAND);
|
||||
if (value instanceof String command) {
|
||||
result.setCommand(createValidCommand(command));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case GroupCommandTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(GroupCommandTriggerHandler.CFG_GROUPNAME);
|
||||
if (value instanceof String str) {
|
||||
GroupMemberCommandEventTrigger result = factory.createGroupMemberCommandEventTrigger();
|
||||
result.setGroup(str);
|
||||
value = trigger.getConfiguration().get(GroupCommandTriggerHandler.CFG_COMMAND);
|
||||
if (value instanceof String command) {
|
||||
result.setCommand(createValidCommand(command));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ItemStateTriggerHandler.UPDATE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME);
|
||||
if (value instanceof String str) {
|
||||
UpdateEventTrigger result = factory.createUpdateEventTrigger();
|
||||
result.setItem(str);
|
||||
value = trigger.getConfiguration().get(ItemStateTriggerHandler.CFG_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setState(createValidState(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME);
|
||||
if (value instanceof String str) {
|
||||
ChangedEventTrigger result = factory.createChangedEventTrigger();
|
||||
result.setItem(str);
|
||||
value = trigger.getConfiguration().get(ItemStateTriggerHandler.CFG_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setNewState(createValidState(state));
|
||||
}
|
||||
value = trigger.getConfiguration().get(ItemStateTriggerHandler.CFG_PREVIOUS_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setOldState(createValidState(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(GroupStateTriggerHandler.CFG_GROUPNAME);
|
||||
if (value instanceof String str) {
|
||||
GroupMemberUpdateEventTrigger result = factory.createGroupMemberUpdateEventTrigger();
|
||||
result.setGroup(str);
|
||||
value = trigger.getConfiguration().get(GroupStateTriggerHandler.CFG_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setState(createValidState(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(GroupStateTriggerHandler.CFG_GROUPNAME);
|
||||
if (value instanceof String str) {
|
||||
GroupMemberChangedEventTrigger result = factory.createGroupMemberChangedEventTrigger();
|
||||
result.setGroup(str);
|
||||
value = trigger.getConfiguration().get(GroupStateTriggerHandler.CFG_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setNewState(createValidState(state));
|
||||
}
|
||||
value = trigger.getConfiguration().get(GroupStateTriggerHandler.CFG_PREVIOUS_STATE);
|
||||
if (value instanceof String state) {
|
||||
result.setOldState(createValidState(state));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case GenericCronTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(GenericCronTriggerHandler.CFG_CRON_EXPRESSION);
|
||||
if (value instanceof String str) {
|
||||
TimerTrigger result = factory.createTimerTrigger();
|
||||
if ("0 0 12 * * ?".equals(str)) {
|
||||
result.setTime("noon");
|
||||
} else if ("0 0 0 * * ?".equals(str)) {
|
||||
result.setTime("midnight");
|
||||
} else {
|
||||
result.setCron(str);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case TimeOfDayTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(TimeOfDayTriggerHandler.CFG_TIME);
|
||||
if (value instanceof String str) {
|
||||
TimerTrigger result = factory.createTimerTrigger();
|
||||
Matcher m = TIME_PATTERN.matcher(str);
|
||||
String time = m.find() && m.groupCount() > 1 && m.group(1).length() < 2 ? '0' + str : str;
|
||||
if ("12:00".equals(time)) {
|
||||
result.setTime("noon");
|
||||
} else if ("00:00".equals(time)) {
|
||||
result.setTime("midnight");
|
||||
} else {
|
||||
result.setTime(time);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case DateTimeTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(DateTimeTriggerHandler.CONFIG_ITEM_NAME);
|
||||
if (value instanceof String str) {
|
||||
DateTimeTrigger result = factory.createDateTimeTrigger();
|
||||
result.setItem(str);
|
||||
value = trigger.getConfiguration().get(DateTimeTriggerHandler.CONFIG_TIME_ONLY);
|
||||
if (value instanceof Boolean timeOnly) {
|
||||
result.setTimeOnly(timeOnly);
|
||||
}
|
||||
value = trigger.getConfiguration().get(DateTimeTriggerHandler.CONFIG_OFFSET);
|
||||
if (value instanceof String offset) {
|
||||
result.setOffset(offset);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ChannelEventTriggerHandler.MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ChannelEventTriggerHandler.CFG_CHANNEL);
|
||||
if (value instanceof String str) {
|
||||
EventEmittedTrigger result = factory.createEventEmittedTrigger();
|
||||
result.setChannel(str);
|
||||
value = trigger.getConfiguration().get(ChannelEventTriggerHandler.CFG_CHANNEL_EVENT);
|
||||
if (value instanceof String event) {
|
||||
ValidTrigger trg = factory.createValidTrigger();
|
||||
trg.setValue(event);
|
||||
result.setTrigger(trg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ThingStatusTriggerHandler.CFG_THING_UID);
|
||||
if (value instanceof String str) {
|
||||
ThingStateUpdateEventTrigger result = factory.createThingStateUpdateEventTrigger();
|
||||
result.setThing(str);
|
||||
value = trigger.getConfiguration().get(ThingStatusTriggerHandler.CFG_STATUS);
|
||||
if (value instanceof String status) {
|
||||
result.setState(status);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
case ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID:
|
||||
value = trigger.getConfiguration().get(ThingStatusTriggerHandler.CFG_THING_UID);
|
||||
if (value instanceof String str) {
|
||||
ThingStateChangedEventTrigger result = factory.createThingStateChangedEventTrigger();
|
||||
result.setThing(str);
|
||||
value = trigger.getConfiguration().get(ThingStatusTriggerHandler.CFG_STATUS);
|
||||
if (value instanceof String status) {
|
||||
result.setNewState(status);
|
||||
value = trigger.getConfiguration().get(ThingStatusTriggerHandler.CFG_PREVIOUS_STATUS);
|
||||
if (value instanceof String previousStatus) {
|
||||
result.setOldState(previousStatus);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SerializationException("Invalid trigger: " + trigger);
|
||||
default:
|
||||
throw new SerializationException("Unsupported trigger: " + trigger);
|
||||
}
|
||||
}
|
||||
|
||||
private org.openhab.core.model.rule.rules.Condition buildModelCondition(Condition condition)
|
||||
throws SerializationException {
|
||||
String type = condition.getTypeUID();
|
||||
Object value;
|
||||
RulesFactory factory = RulesFactory.eINSTANCE;
|
||||
int i;
|
||||
|
||||
switch (type) {
|
||||
case TimeOfDayConditionHandler.MODULE_TYPE_ID:
|
||||
TimeOfDayCondition todCond = factory.createTimeOfDayCondition();
|
||||
value = condition.getConfiguration().get(TimeOfDayConditionHandler.CFG_START_TIME);
|
||||
if (value instanceof String start) {
|
||||
Matcher m = TIME_PATTERN.matcher(start);
|
||||
String time = m.find() && m.groupCount() > 1 && m.group(1).length() < 2 ? '0' + start : start;
|
||||
todCond.setStart(time);
|
||||
}
|
||||
value = condition.getConfiguration().get(TimeOfDayConditionHandler.CFG_END_TIME);
|
||||
if (value instanceof String end) {
|
||||
Matcher m = TIME_PATTERN.matcher(end);
|
||||
String time = m.find() && m.groupCount() > 1 && m.group(1).length() < 2 ? '0' + end : end;
|
||||
todCond.setEnd(time);
|
||||
}
|
||||
return todCond;
|
||||
case DayOfWeekConditionHandler.MODULE_TYPE_ID:
|
||||
DayOfWeekCondition dowCond = factory.createDayOfWeekCondition();
|
||||
value = condition.getConfiguration().get(DayOfWeekConditionHandler.CFG_DAYS);
|
||||
if (value == null) {
|
||||
value = List.of();
|
||||
}
|
||||
if (value instanceof List<?> weekDays) {
|
||||
EList<String> eWeekDays = dowCond.getWeekDays();
|
||||
for (Object dayObject : weekDays) {
|
||||
if (dayObject instanceof String day) {
|
||||
switch (day) {
|
||||
case "MON":
|
||||
eWeekDays.add("Monday");
|
||||
break;
|
||||
case "TUE":
|
||||
eWeekDays.add("Tuesday");
|
||||
break;
|
||||
case "WED":
|
||||
eWeekDays.add("Wednesday");
|
||||
break;
|
||||
case "THU":
|
||||
eWeekDays.add("Thursday");
|
||||
break;
|
||||
case "FRI":
|
||||
eWeekDays.add("Friday");
|
||||
break;
|
||||
case "SAT":
|
||||
eWeekDays.add("Saturday");
|
||||
break;
|
||||
case "SUN":
|
||||
eWeekDays.add("Sunday");
|
||||
break;
|
||||
default:
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return dowCond;
|
||||
case EphemerisConditionHandler.WEEKDAY_MODULE_TYPE_ID:
|
||||
WeekdayCondition wdCond = factory.createWeekdayCondition();
|
||||
wdCond.setType("weekday");
|
||||
value = condition.getConfiguration().get("offset");
|
||||
if (value instanceof Number offset) {
|
||||
if ((i = offset.intValue()) != 0) {
|
||||
wdCond.setOffset(Integer.toString(i));
|
||||
}
|
||||
} else if (value != null) {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return wdCond;
|
||||
case EphemerisConditionHandler.WEEKEND_MODULE_TYPE_ID:
|
||||
WeekdayCondition weCond = factory.createWeekdayCondition();
|
||||
weCond.setType("weekend");
|
||||
value = condition.getConfiguration().get("offset");
|
||||
if (value instanceof Number offset) {
|
||||
if ((i = offset.intValue()) != 0) {
|
||||
weCond.setOffset(Integer.toString(i));
|
||||
}
|
||||
} else if (value != null) {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return weCond;
|
||||
case EphemerisConditionHandler.HOLIDAY_MODULE_TYPE_ID:
|
||||
HolidayCondition hdCond = factory.createHolidayCondition();
|
||||
hdCond.setHoliday("holiday");
|
||||
hdCond.setNegation(false);
|
||||
value = condition.getConfiguration().get("offset");
|
||||
if (value instanceof Number offset) {
|
||||
if ((i = offset.intValue()) != 0) {
|
||||
hdCond.setOffset(Integer.toString(i));
|
||||
}
|
||||
} else if (value != null) {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return hdCond;
|
||||
case EphemerisConditionHandler.NOT_HOLIDAY_MODULE_TYPE_ID:
|
||||
HolidayCondition nhdCond = factory.createHolidayCondition();
|
||||
nhdCond.setHoliday("holiday");
|
||||
nhdCond.setNegation(true);
|
||||
value = condition.getConfiguration().get("offset");
|
||||
if (value instanceof Number offset) {
|
||||
if ((i = offset.intValue()) != 0) {
|
||||
nhdCond.setOffset(Integer.toString(i));
|
||||
}
|
||||
} else if (value != null) {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return nhdCond;
|
||||
case EphemerisConditionHandler.DAYSET_MODULE_TYPE_ID:
|
||||
InDaysetCondition idsCond = factory.createInDaysetCondition();
|
||||
value = condition.getConfiguration().get("dayset");
|
||||
if (value instanceof String dayset) {
|
||||
idsCond.setDayset(dayset);
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
value = condition.getConfiguration().get("offset");
|
||||
if (value instanceof Number offset) {
|
||||
if ((i = offset.intValue()) != 0) {
|
||||
idsCond.setOffset(Integer.toString(i));
|
||||
}
|
||||
} else if (value != null) {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return idsCond;
|
||||
case IntervalConditionHandler.MODULE_TYPE_ID:
|
||||
IntervalCondition ivCond = factory.createIntervalCondition();
|
||||
value = condition.getConfiguration().get(IntervalConditionHandler.CFG_MIN_INTERVAL);
|
||||
if (value instanceof Number interval) {
|
||||
ivCond.setInterval(interval.intValue());
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
return ivCond;
|
||||
case ThingStatusConditionHandler.THING_STATUS_CONDITION:
|
||||
ThingStatusCondition tsCond = factory.createThingStatusCondition();
|
||||
value = condition.getConfiguration().get(ThingStatusConditionHandler.CFG_THING_UID);
|
||||
if (value instanceof String thingUid) {
|
||||
tsCond.setThing(thingUid);
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
value = condition.getConfiguration().get(ThingStatusConditionHandler.CFG_STATUS);
|
||||
if (value instanceof String status) {
|
||||
tsCond.setStatus(status);
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
value = condition.getConfiguration().get(ThingStatusConditionHandler.CFG_OPERATOR);
|
||||
tsCond.setNegation(value instanceof String op && "!=".equals(op));
|
||||
return tsCond;
|
||||
case ItemStateConditionHandler.ITEM_STATE_CONDITION:
|
||||
ItemStateCondition isCond = factory.createItemStateCondition();
|
||||
value = condition.getConfiguration().get(ItemStateConditionHandler.ITEM_NAME);
|
||||
if (value instanceof String itemName) {
|
||||
isCond.setItem(itemName);
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
value = condition.getConfiguration().get(ItemStateConditionHandler.STATE);
|
||||
if (value instanceof String state) {
|
||||
isCond.setState(createValidState(state));
|
||||
} else {
|
||||
throw new SerializationException("Invalid condition: " + condition);
|
||||
}
|
||||
value = condition.getConfiguration().get(ItemStateConditionHandler.OPERATOR);
|
||||
isCond.setOperator(value instanceof String op && !op.isBlank() ? op : "=");
|
||||
return isCond;
|
||||
default:
|
||||
throw new SerializationException("Unsupported condition: " + condition);
|
||||
}
|
||||
}
|
||||
|
||||
private ValidState createValidState(String stateValue) {
|
||||
ValidState result;
|
||||
if (NUMERIC_PATTERN.matcher(stateValue).matches()) {
|
||||
result = RulesFactory.eINSTANCE.createValidStateNumber();
|
||||
} else if (enumStates.contains(stateValue)) {
|
||||
result = RulesFactory.eINSTANCE.createValidStateId();
|
||||
} else {
|
||||
result = RulesFactory.eINSTANCE.createValidStateString();
|
||||
}
|
||||
result.setValue(stateValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
private ValidCommand createValidCommand(String commandValue) {
|
||||
ValidCommand result;
|
||||
if (NUMERIC_PATTERN.matcher(commandValue).matches()) {
|
||||
result = RulesFactory.eINSTANCE.createValidCommandNumber();
|
||||
} else if (enumCommands.contains(commandValue)) {
|
||||
result = RulesFactory.eINSTANCE.createValidCommandId();
|
||||
} else {
|
||||
result = RulesFactory.eINSTANCE.createValidCommandString();
|
||||
}
|
||||
result.setValue(commandValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
private XBlockExpression createPlaceholder(String placeholderLiteral) {
|
||||
// Creates expression: 'val placeholder="<placeholderLiteral>"'
|
||||
XbaseFactory factory = XbaseFactory.eINSTANCE;
|
||||
XBlockExpression result = factory.createXBlockExpression();
|
||||
XVariableDeclaration varDecl = factory.createXVariableDeclaration();
|
||||
varDecl.setName("placeholder");
|
||||
XStringLiteral stringLit = factory.createXStringLiteral();
|
||||
stringLit.setValue(placeholderLiteral);
|
||||
varDecl.setRight(stringLit);
|
||||
result.getExpressions().add(varDecl);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ Export-Package: org.openhab.core.model.rule,\
|
||||
org.openhab.core.model.rule.validation
|
||||
Import-Package: \
|
||||
org.openhab.core.automation,\
|
||||
org.openhab.core.automation.converter,\
|
||||
org.openhab.core.automation.module.script.rulesupport.shared,\
|
||||
org.openhab.core.automation.util,\
|
||||
org.openhab.core.common,\
|
||||
|
||||
+6
@@ -37,6 +37,8 @@ import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider
|
||||
import org.eclipse.xtext.xbase.interpreter.IExpressionInterpreter
|
||||
import org.eclipse.xtext.xbase.scoping.batch.ImplicitlyImportedFeatures
|
||||
import org.eclipse.xtext.xbase.typesystem.computation.ITypeComputer
|
||||
import org.eclipse.xtext.formatting.IFormatter
|
||||
import org.openhab.core.model.rule.formatting.RulesFormatter
|
||||
|
||||
/**
|
||||
* Use this class to register components to be used at runtime / without the Equinox extension registry.
|
||||
@@ -56,6 +58,10 @@ import org.eclipse.xtext.xbase.typesystem.computation.ITypeComputer
|
||||
return ScriptImplicitlyImportedTypes
|
||||
}
|
||||
|
||||
override Class<? extends IFormatter> bindIFormatter() {
|
||||
return RulesFormatter;
|
||||
}
|
||||
|
||||
override Class<? extends IGenerator> bindIGenerator() {
|
||||
return NullGenerator
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.rule.formatting;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.formatting.IElementMatcherProvider.IElementMatcher;
|
||||
import org.eclipse.xtext.formatting.impl.AbstractFormattingConfig.ElementLocator;
|
||||
import org.eclipse.xtext.formatting.impl.AbstractFormattingConfig.ElementPattern;
|
||||
import org.eclipse.xtext.formatting.impl.FormattingConfig;
|
||||
import org.eclipse.xtext.formatting.impl.FormattingConfigBasedStream;
|
||||
import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper;
|
||||
import org.eclipse.xtext.parsetree.reconstr.ITokenStream;
|
||||
|
||||
/**
|
||||
* This class exists only to override indentation logic.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RuleFormattingConfigBasedStream extends FormattingConfigBasedStream {
|
||||
|
||||
public RuleFormattingConfigBasedStream(@Nullable ITokenStream out, @Nullable String initialIndentation,
|
||||
FormattingConfig cfg, IElementMatcher<@Nullable ElementPattern> matcher,
|
||||
IHiddenTokenHelper hiddenTokenHelper, boolean preserveSpaces) {
|
||||
super(out, initialIndentation, cfg, matcher, hiddenTokenHelper, preserveSpaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNullByDefault({}) Set<ElementLocator> collectLocators(EObject ele) {
|
||||
|
||||
Set<@Nullable ElementLocator> result = super.collectLocators(ele);
|
||||
if (indentationLevel < 0) {
|
||||
indentationLevel = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.rule.formatting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.xtext.Keyword;
|
||||
import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter;
|
||||
import org.eclipse.xtext.formatting.impl.AbstractFormattingConfig;
|
||||
import org.eclipse.xtext.formatting.impl.FormattingConfig;
|
||||
import org.eclipse.xtext.parsetree.reconstr.ITokenStream;
|
||||
import org.eclipse.xtext.util.Pair;
|
||||
import org.eclipse.xtext.xbase.lib.Extension;
|
||||
import org.eclipse.xtext.xtext.XtextFormatter;
|
||||
import org.openhab.core.model.rule.services.RulesGrammarAccess;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
/**
|
||||
* This class contains custom formatting description.
|
||||
*
|
||||
* see : http://www.eclipse.org/Xtext/documentation.html#formatting
|
||||
* on how and when to use it
|
||||
*
|
||||
* Also see {@link XtextFormatter} as an example
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RulesFormatter extends AbstractDeclarativeFormatter {
|
||||
|
||||
@Inject
|
||||
@Extension
|
||||
private RulesGrammarAccess rulesGrammarAccess;
|
||||
|
||||
@Override
|
||||
protected void configureFormatting(@NonNullByDefault({}) FormattingConfig c) {
|
||||
c.setLinewrap(1, 1, 2).before(rulesGrammarAccess.getRuleModelRule());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleModelRule());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getXImportDeclarationRule());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getXFunctionTypeRefRule());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getXBlockExpressionRule());
|
||||
c.setLinewrap(1, 2, 2).before(rulesGrammarAccess.getRuleAccess().getRuleKeyword_0());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleAccess().getOrKeyword_4_2_0());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleAccess().getAndKeyword_5_4_0());
|
||||
c.setLinewrap(1, 1, 2).before(rulesGrammarAccess.getRuleAccess().getWhenKeyword_4_0());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleAccess().getWhenKeyword_4_0());
|
||||
c.setLinewrap(1, 1, 2).before(rulesGrammarAccess.getRuleAccess().getButKeyword_5_0());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleAccess().getIfKeyword_5_2());
|
||||
c.setLinewrap(1, 1, 2).before(rulesGrammarAccess.getRuleAccess().getThenKeyword_6());
|
||||
c.setLinewrap(1, 1, 2).after(rulesGrammarAccess.getRuleAccess().getThenKeyword_6());
|
||||
c.setLinewrap(1, 1, 2).before(rulesGrammarAccess.getRuleAccess().getEndKeyword_8());
|
||||
|
||||
after(c.setIndentationIncrement(), "{");
|
||||
before(c.setIndentationDecrement(), "}");
|
||||
c.setIndentationIncrement().after(rulesGrammarAccess.getRuleAccess().getWhenKeyword_4_0());
|
||||
c.setIndentationDecrement().before(rulesGrammarAccess.getRuleAccess().getButKeyword_5_0());
|
||||
c.setIndentationIncrement().after(rulesGrammarAccess.getRuleAccess().getIfKeyword_5_2());
|
||||
c.setIndentationDecrement().before(rulesGrammarAccess.getRuleAccess().getThenKeyword_6());
|
||||
c.setIndentationIncrement().after(rulesGrammarAccess.getRuleAccess().getThenKeyword_6());
|
||||
c.setIndentationDecrement().before(rulesGrammarAccess.getRuleAccess().getEndKeyword_8());
|
||||
|
||||
before(c.setLinewrap(), "}");
|
||||
|
||||
withinKeywordPairs(c.setNoSpace(), "[", "]");
|
||||
around(c.setNoSpace(), "=", ":");
|
||||
before(c.setNoSpace(), ",");
|
||||
|
||||
c.setAutoLinewrap(120);
|
||||
|
||||
c.setLinewrap(0, 1, 2).before(rulesGrammarAccess.getSL_COMMENTRule());
|
||||
c.setLinewrap(0, 1, 2).before(rulesGrammarAccess.getML_COMMENTRule());
|
||||
c.setLinewrap(0, 1, 1).after(rulesGrammarAccess.getML_COMMENTRule());
|
||||
}
|
||||
|
||||
public void withinKeywordPairs(FormattingConfig.NoSpaceLocator locator, String leftKW, String rightKW) {
|
||||
List<Pair<Keyword, Keyword>> keywordPairs = rulesGrammarAccess.findKeywordPairs(leftKW, rightKW);
|
||||
for (Pair<Keyword, Keyword> pair : keywordPairs) {
|
||||
{
|
||||
locator.after(pair.getFirst());
|
||||
locator.before(pair.getSecond());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void around(final AbstractFormattingConfig.ElementLocator locator, String... listKW) {
|
||||
List<Keyword> keywords = rulesGrammarAccess.findKeywords(listKW);
|
||||
for (Keyword keyword : keywords) {
|
||||
locator.around(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
public void after(final AbstractFormattingConfig.ElementLocator locator, String... listKW) {
|
||||
List<Keyword> keywords = rulesGrammarAccess.findKeywords(listKW);
|
||||
for (Keyword keyword : keywords) {
|
||||
locator.after(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
public void before(AbstractFormattingConfig.ElementLocator locator, String... listKW) {
|
||||
List<Keyword> keywords = rulesGrammarAccess.findKeywords(listKW);
|
||||
for (Keyword keyword : keywords) {
|
||||
locator.before(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITokenStream createFormatterStream(@Nullable String indent, @Nullable ITokenStream out,
|
||||
boolean preserveWhitespaces) {
|
||||
return new RuleFormattingConfigBasedStream(out, indent, getConfig(), createMatcher(), getHiddenTokenHelper(),
|
||||
preserveWhitespaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITokenStream createFormatterStream(@Nullable EObject context, @Nullable String indent,
|
||||
@Nullable ITokenStream out, boolean preserveWhitespaces) {
|
||||
|
||||
// We have no choice but to call super() and then ignore the returned ITokenStream. The reason is that
|
||||
// contextResourceURI is private, so we can't set it from here, making the call to super() required. But,
|
||||
// we don't want the returned instance because it's of the wrong type, so we have to create another one.
|
||||
|
||||
super.createFormatterStream(context, indent, out, preserveWhitespaces);
|
||||
|
||||
return new RuleFormattingConfigBasedStream(out, indent, getConfig(), createMatcher(), getHiddenTokenHelper(),
|
||||
preserveWhitespaces);
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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
|
||||
*/
|
||||
/*
|
||||
* generated by Xtext
|
||||
*/
|
||||
package org.openhab.core.model.rule.formatting
|
||||
|
||||
import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter
|
||||
import org.eclipse.xtext.formatting.impl.FormattingConfig
|
||||
// import com.google.inject.Inject;
|
||||
// import org.openhab.core.model.rule.services.RulesGrammarAccess
|
||||
|
||||
/**
|
||||
* This class contains custom formatting description.
|
||||
*
|
||||
* see : http://www.eclipse.org/Xtext/documentation.html#formatting
|
||||
* on how and when to use it
|
||||
*
|
||||
* Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example
|
||||
*/
|
||||
class RulesFormatter extends AbstractDeclarativeFormatter {
|
||||
|
||||
// @Inject extension RulesGrammarAccess
|
||||
|
||||
override protected void configureFormatting(FormattingConfig c) {
|
||||
// It's usually a good idea to activate the following three statements.
|
||||
// They will add and preserve newlines around comments
|
||||
// c.setLinewrap(0, 1, 2).before(SL_COMMENTRule)
|
||||
// c.setLinewrap(0, 1, 2).before(ML_COMMENTRule)
|
||||
// c.setLinewrap(0, 1, 1).after(ML_COMMENTRule)
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -59,14 +59,15 @@ public class YamlConfigDescriptionDTO {
|
||||
* Creates a new instance based on the specified {@link ConfigDescription}.
|
||||
*
|
||||
* @param configDescription the {@link ConfigDescription}.
|
||||
* @param includeDefault whether boolean values with the default value should be included.
|
||||
*/
|
||||
public YamlConfigDescriptionDTO(@NonNull ConfigDescription configDescription) {
|
||||
public YamlConfigDescriptionDTO(@NonNull ConfigDescription configDescription, boolean includeDefault) {
|
||||
this.uri = toDecodedString(configDescription.getUID());
|
||||
List<@NonNull ConfigDescriptionParameter> fromParams = configDescription.getParameters();
|
||||
if (!fromParams.isEmpty()) {
|
||||
Map<String, YamlConfigDescriptionParameterDTO> params = new LinkedHashMap<>();
|
||||
for (ConfigDescriptionParameter param : fromParams) {
|
||||
params.put(param.getName(), new YamlConfigDescriptionParameterDTO(param));
|
||||
params.put(param.getName(), new YamlConfigDescriptionParameterDTO(param, includeDefault));
|
||||
}
|
||||
this.params = params;
|
||||
}
|
||||
@@ -74,7 +75,8 @@ public class YamlConfigDescriptionDTO {
|
||||
if (!fromParamGroups.isEmpty()) {
|
||||
Map<String, YamlConfigDescriptionParameterGroupDTO> paramGroups = new LinkedHashMap<>();
|
||||
for (ConfigDescriptionParameterGroup paramGroup : fromParamGroups) {
|
||||
paramGroups.put(paramGroup.getName(), new YamlConfigDescriptionParameterGroupDTO(paramGroup));
|
||||
paramGroups.put(paramGroup.getName(),
|
||||
new YamlConfigDescriptionParameterGroupDTO(paramGroup, includeDefault));
|
||||
}
|
||||
this.paramGroups = paramGroups;
|
||||
}
|
||||
|
||||
+32
-4
@@ -39,13 +39,20 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
*/
|
||||
public class YamlConfigDescriptionParameterDTO {
|
||||
|
||||
private static final Boolean REQUIRED_DEFAULT = Boolean.FALSE;
|
||||
private static final Boolean READ_ONLY_DEFAULT = Boolean.FALSE;
|
||||
private static final Boolean MULTIPLE_DEFAULT = Boolean.FALSE;
|
||||
private static final Boolean LIMIT_TO_OPTIONS_DEFAULT = Boolean.TRUE;
|
||||
private static final Boolean ADVANCED_DEFAULT = Boolean.FALSE;
|
||||
private static final Boolean VERIFY_DEFAULT = Boolean.FALSE;
|
||||
|
||||
public String context;
|
||||
@JsonProperty("default")
|
||||
@JsonAlias("defaultValue")
|
||||
public String defaultValue;
|
||||
public String description;
|
||||
public String label;
|
||||
public boolean required;
|
||||
public Boolean required;
|
||||
public Type type;
|
||||
public BigDecimal min;
|
||||
public BigDecimal max;
|
||||
@@ -75,17 +82,27 @@ public class YamlConfigDescriptionParameterDTO {
|
||||
* Creates a new instance based on the specified {@link ConfigDescriptionParameter}.
|
||||
*
|
||||
* @param parameter the {@link ConfigDescriptionParameter}.
|
||||
* @param includeDefault whether boolean values with the default value should be included.
|
||||
*/
|
||||
public YamlConfigDescriptionParameterDTO(@NonNull ConfigDescriptionParameter parameter) {
|
||||
public YamlConfigDescriptionParameterDTO(@NonNull ConfigDescriptionParameter parameter, boolean includeDefault) {
|
||||
this.type = parameter.getType();
|
||||
this.min = parameter.getMinimum();
|
||||
this.max = parameter.getMaximum();
|
||||
this.step = parameter.getStepSize();
|
||||
this.pattern = parameter.getPattern();
|
||||
this.readOnly = parameter.isReadOnly();
|
||||
if (!includeDefault && READ_ONLY_DEFAULT.equals(this.readOnly)) {
|
||||
this.readOnly = null;
|
||||
}
|
||||
this.multiple = parameter.isMultiple();
|
||||
if (!includeDefault && MULTIPLE_DEFAULT.equals(this.multiple)) {
|
||||
this.multiple = null;
|
||||
}
|
||||
this.context = parameter.getContext();
|
||||
this.required = parameter.isRequired();
|
||||
if (!includeDefault && REQUIRED_DEFAULT.equals(this.required)) {
|
||||
this.required = null;
|
||||
}
|
||||
this.defaultValue = parameter.getDefault();
|
||||
this.label = parameter.getLabel();
|
||||
this.description = parameter.getDescription();
|
||||
@@ -107,11 +124,20 @@ public class YamlConfigDescriptionParameterDTO {
|
||||
}
|
||||
this.groupName = parameter.getGroupName();
|
||||
this.advanced = parameter.isAdvanced();
|
||||
if (!includeDefault && ADVANCED_DEFAULT.equals(this.advanced)) {
|
||||
this.advanced = null;
|
||||
}
|
||||
this.limitToOptions = parameter.getLimitToOptions();
|
||||
if (!includeDefault && LIMIT_TO_OPTIONS_DEFAULT.equals(this.limitToOptions)) {
|
||||
this.limitToOptions = null;
|
||||
}
|
||||
this.multipleLimit = parameter.getMultipleLimit();
|
||||
this.unit = parameter.getUnit();
|
||||
this.unitLabel = parameter.getUnitLabel();
|
||||
this.verify = parameter.isVerifyable();
|
||||
if (!includeDefault && VERIFY_DEFAULT.equals(this.verify)) {
|
||||
this.verify = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,7 +163,7 @@ public class YamlConfigDescriptionParameterDTO {
|
||||
&& Objects.equals(max, other.max) && Objects.equals(min, other.min)
|
||||
&& Objects.equals(multiple, other.multiple) && Objects.equals(multipleLimit, other.multipleLimit)
|
||||
&& Objects.equals(options, other.options) && Objects.equals(pattern, other.pattern)
|
||||
&& Objects.equals(readOnly, other.readOnly) && required == other.required
|
||||
&& Objects.equals(readOnly, other.readOnly) && Objects.equals(required, other.required)
|
||||
&& Objects.equals(step, other.step) && type == other.type && Objects.equals(unit, other.unit)
|
||||
&& Objects.equals(unitLabel, other.unitLabel) && Objects.equals(verify, other.verify);
|
||||
}
|
||||
@@ -158,7 +184,9 @@ public class YamlConfigDescriptionParameterDTO {
|
||||
if (label != null) {
|
||||
builder.append("label=").append(label).append(", ");
|
||||
}
|
||||
builder.append("required=").append(required).append(", ");
|
||||
if (required != null) {
|
||||
builder.append("required=").append(required).append(", ");
|
||||
}
|
||||
if (type != null) {
|
||||
builder.append("type=").append(type).append(", ");
|
||||
}
|
||||
|
||||
+8
-1
@@ -27,6 +27,8 @@ import org.openhab.core.config.core.dto.ConfigDescriptionParameterGroupDTO;
|
||||
*/
|
||||
public class YamlConfigDescriptionParameterGroupDTO {
|
||||
|
||||
private static final Boolean ADVANCED_DEFAULT = Boolean.FALSE;
|
||||
|
||||
public String context;
|
||||
public Boolean advanced;
|
||||
public String label;
|
||||
@@ -42,10 +44,15 @@ public class YamlConfigDescriptionParameterGroupDTO {
|
||||
* Creates a new instance based on the specified {@link ConfigDescriptionParameterGroup}.
|
||||
*
|
||||
* @param parameterGroup the {@link ConfigDescriptionParameterGroup}.
|
||||
* @param includeDefault whether boolean values with the default value should be included.
|
||||
*/
|
||||
public YamlConfigDescriptionParameterGroupDTO(@NonNull ConfigDescriptionParameterGroup parameterGroup) {
|
||||
public YamlConfigDescriptionParameterGroupDTO(@NonNull ConfigDescriptionParameterGroup parameterGroup,
|
||||
boolean includeDefault) {
|
||||
this.context = parameterGroup.getContext();
|
||||
this.advanced = parameterGroup.isAdvanced();
|
||||
if (!includeDefault && ADVANCED_DEFAULT.equals(this.advanced)) {
|
||||
this.advanced = null;
|
||||
}
|
||||
this.label = parameterGroup.getLabel();
|
||||
this.description = parameterGroup.getDescription();
|
||||
}
|
||||
|
||||
+2
@@ -26,6 +26,7 @@ import org.openhab.core.automation.internal.module.handler.DateTimeTriggerHandle
|
||||
import org.openhab.core.automation.internal.module.handler.DayOfWeekConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.EphemerisConditionHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GenericEventTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupCommandTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.GroupStateTriggerHandler;
|
||||
import org.openhab.core.automation.internal.module.handler.IntervalConditionHandler;
|
||||
@@ -82,6 +83,7 @@ public class ModuleTypeAliases {
|
||||
{ "T", "ChannelEvent", ChannelEventTriggerHandler.MODULE_TYPE_ID }, //
|
||||
{ "T", "Cron", GenericCronTriggerHandler.MODULE_TYPE_ID }, //
|
||||
{ "T", "DateTime", DateTimeTriggerHandler.MODULE_TYPE_ID }, //
|
||||
{ "T", "GenericEvent", GenericEventTriggerHandler.MODULE_TYPE_ID }, //
|
||||
{ "T", "MemberReceivedCommand", GroupCommandTriggerHandler.MODULE_TYPE_ID }, //
|
||||
{ "T", "MemberChanged", GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID }, //
|
||||
{ "T", "MemberUpdated", GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID }, //
|
||||
|
||||
+17
-9
@@ -17,6 +17,7 @@ import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
|
||||
/**
|
||||
* The {@link YamlActionDTO} is a data transfer object used to serialize an action in a YAML configuration file.
|
||||
@@ -31,8 +32,15 @@ public class YamlActionDTO extends YamlModuleDTO {
|
||||
}
|
||||
|
||||
public YamlActionDTO(@NonNull Action action) {
|
||||
super(action);
|
||||
this(action, RuleSerializationOption.NORMAL);
|
||||
}
|
||||
|
||||
public YamlActionDTO(@NonNull Action action, RuleSerializationOption option) {
|
||||
super(action, option);
|
||||
this.inputs = action.getInputs();
|
||||
if (option != RuleSerializationOption.INCLUDE_ALL && this.inputs.isEmpty()) {
|
||||
this.inputs = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,23 +70,23 @@ public class YamlActionDTO extends YamlModuleDTO {
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
|
||||
builder.append(" [");
|
||||
if (inputs != null) {
|
||||
builder.append("inputs=").append(inputs).append(", ");
|
||||
}
|
||||
if (id != null) {
|
||||
builder.append("id=").append(id).append(", ");
|
||||
builder.append("id=").append(id);
|
||||
}
|
||||
if (inputs != null) {
|
||||
builder.append(", inputs=").append(inputs);
|
||||
}
|
||||
if (type != null) {
|
||||
builder.append("type=").append(type).append(", ");
|
||||
builder.append(", type=").append(type);
|
||||
}
|
||||
if (label != null) {
|
||||
builder.append("label=").append(label).append(", ");
|
||||
builder.append(", label=").append(label);
|
||||
}
|
||||
if (description != null) {
|
||||
builder.append("description=").append(description).append(", ");
|
||||
builder.append(", description=").append(description);
|
||||
}
|
||||
if (config != null) {
|
||||
builder.append("config=").append(config);
|
||||
builder.append(", config=").append(config);
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
|
||||
+17
-9
@@ -17,6 +17,7 @@ import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
|
||||
/**
|
||||
* The {@link YamlConditionDTO} is a data transfer object used to serialize a condition in a YAML configuration file.
|
||||
@@ -31,8 +32,15 @@ public class YamlConditionDTO extends YamlModuleDTO {
|
||||
}
|
||||
|
||||
public YamlConditionDTO(@NonNull Condition condition) {
|
||||
super(condition);
|
||||
this(condition, RuleSerializationOption.NORMAL);
|
||||
}
|
||||
|
||||
public YamlConditionDTO(@NonNull Condition condition, RuleSerializationOption option) {
|
||||
super(condition, option);
|
||||
this.inputs = condition.getInputs();
|
||||
if (option != RuleSerializationOption.INCLUDE_ALL && this.inputs.isEmpty()) {
|
||||
this.inputs = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,23 +70,23 @@ public class YamlConditionDTO extends YamlModuleDTO {
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
|
||||
builder.append(" [");
|
||||
if (inputs != null) {
|
||||
builder.append("inputs=").append(inputs).append(", ");
|
||||
}
|
||||
if (id != null) {
|
||||
builder.append("id=").append(id).append(", ");
|
||||
builder.append("id=").append(id);
|
||||
}
|
||||
if (inputs != null) {
|
||||
builder.append(", inputs=").append(inputs);
|
||||
}
|
||||
if (type != null) {
|
||||
builder.append("type=").append(type).append(", ");
|
||||
builder.append(", type=").append(type);
|
||||
}
|
||||
if (label != null) {
|
||||
builder.append("label=").append(label).append(", ");
|
||||
builder.append(", label=").append(label);
|
||||
}
|
||||
if (description != null) {
|
||||
builder.append("description=").append(description).append(", ");
|
||||
builder.append(", description=").append(description);
|
||||
}
|
||||
if (config != null) {
|
||||
builder.append("config=").append(config);
|
||||
builder.append(", config=").append(config);
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
|
||||
+23
-5
@@ -15,9 +15,11 @@ package org.openhab.core.model.yaml.internal.rules;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.automation.Module;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
|
||||
@@ -28,6 +30,8 @@ import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
*/
|
||||
public class YamlModuleDTO {
|
||||
|
||||
private static final Pattern CONTEXT_COMMENT_PATTERN = Pattern.compile("^// context:.*$\\R", Pattern.MULTILINE);
|
||||
|
||||
public String id;
|
||||
public String label;
|
||||
public String description;
|
||||
@@ -39,6 +43,10 @@ public class YamlModuleDTO {
|
||||
}
|
||||
|
||||
public YamlModuleDTO(@NonNull Module module) {
|
||||
this(module, RuleSerializationOption.NORMAL);
|
||||
}
|
||||
|
||||
public YamlModuleDTO(@NonNull Module module, RuleSerializationOption option) {
|
||||
this.id = module.getId();
|
||||
this.label = module.getLabel();
|
||||
this.description = module.getDescription();
|
||||
@@ -49,6 +57,16 @@ public class YamlModuleDTO {
|
||||
if (!type.equals(typeAlias)) {
|
||||
this.config.put("type", typeAlias);
|
||||
}
|
||||
if (option != RuleSerializationOption.INCLUDE_ALL && "application/vnd.openhab.dsl.rule".equals(type)) {
|
||||
if (this.config.get("script") instanceof String scriptContent) {
|
||||
// Remove the "context comment" inserted into file-based DSL rules
|
||||
this.config.put("script", CONTEXT_COMMENT_PATTERN.matcher(scriptContent).replaceFirst(""));
|
||||
}
|
||||
this.config.remove(Module.SHARED_CONTEXT);
|
||||
}
|
||||
}
|
||||
if (option != RuleSerializationOption.INCLUDE_ALL && this.config.isEmpty()) {
|
||||
this.config = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,19 +94,19 @@ public class YamlModuleDTO {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
|
||||
builder.append(" [");
|
||||
if (id != null) {
|
||||
builder.append("id=").append(id).append(", ");
|
||||
builder.append("id=").append(id);
|
||||
}
|
||||
if (type != null) {
|
||||
builder.append("type=").append(type).append(", ");
|
||||
builder.append(", type=").append(type);
|
||||
}
|
||||
if (label != null) {
|
||||
builder.append("label=").append(label).append(", ");
|
||||
builder.append(", label=").append(label);
|
||||
}
|
||||
if (description != null) {
|
||||
builder.append("description=").append(description).append(", ");
|
||||
builder.append(", description=").append(description);
|
||||
}
|
||||
if (config != null) {
|
||||
builder.append("config=").append(config);
|
||||
builder.append(", config=").append(config);
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
|
||||
+64
-33
@@ -31,6 +31,7 @@ import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.Rule.TemplateState;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
import org.openhab.core.automation.util.RuleUtil;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
import org.openhab.core.io.dto.ModularDTO;
|
||||
@@ -78,46 +79,73 @@ public class YamlRuleDTO implements ModularDTO<YamlRuleDTO, ObjectMapper, JsonNo
|
||||
* @param rule the {@link Rule}.
|
||||
*/
|
||||
public YamlRuleDTO(@NonNull Rule rule) {
|
||||
this(rule, RuleSerializationOption.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance based on the specified {@link Rule}.
|
||||
*
|
||||
* @param rule the {@link Rule}.
|
||||
* @param option the {@link RuleSerializationOption} that decides how the to serialize the {@link Rule}.
|
||||
*/
|
||||
public YamlRuleDTO(@NonNull Rule rule, RuleSerializationOption option) {
|
||||
this.uid = rule.getUID();
|
||||
this.template = rule.getTemplateUID();
|
||||
this.templateState = rule.getTemplateState();
|
||||
this.template = option == RuleSerializationOption.STRIP_TEMPLATE ? null : rule.getTemplateUID();
|
||||
this.templateState = option == RuleSerializationOption.INCLUDE_ALL ? rule.getTemplateState() : null;
|
||||
this.label = rule.getName();
|
||||
this.tags = rule.getTags();
|
||||
Set<@NonNull String> tags = rule.getTags();
|
||||
this.tags = option != RuleSerializationOption.INCLUDE_ALL && tags.isEmpty() ? null : tags;
|
||||
this.description = rule.getDescription();
|
||||
this.visibility = rule.getVisibility();
|
||||
this.config = rule.getConfiguration().getProperties();
|
||||
List<@NonNull ConfigDescriptionParameter> configDescriptions = rule.getConfigurationDescriptions();
|
||||
if (!configDescriptions.isEmpty()) {
|
||||
Map<String, YamlConfigDescriptionParameterDTO> configDescriptionDtos = new LinkedHashMap<>(
|
||||
configDescriptions.size());
|
||||
for (ConfigDescriptionParameter parameter : configDescriptions) {
|
||||
configDescriptionDtos.put(parameter.getName(), new YamlConfigDescriptionParameterDTO(parameter));
|
||||
this.visibility = option == RuleSerializationOption.INCLUDE_ALL || rule.getVisibility() != Visibility.VISIBLE
|
||||
? rule.getVisibility()
|
||||
: null;
|
||||
if (option != RuleSerializationOption.STRIP_TEMPLATE) {
|
||||
this.config = new LinkedHashMap<>(rule.getConfiguration().getProperties());
|
||||
if (option != RuleSerializationOption.INCLUDE_ALL) {
|
||||
this.config.remove(Rule.SOURCE);
|
||||
this.config.remove(Rule.SOURCE_TYPE);
|
||||
if (this.config.isEmpty()) {
|
||||
this.config = null;
|
||||
}
|
||||
}
|
||||
this.configDescriptions = configDescriptionDtos;
|
||||
}
|
||||
List<@NonNull Action> actions = rule.getActions();
|
||||
if (!actions.isEmpty()) {
|
||||
List<YamlActionDTO> actionDtos = new ArrayList<>(actions.size());
|
||||
for (Action action : actions) {
|
||||
actionDtos.add(new YamlActionDTO(action));
|
||||
if (option == RuleSerializationOption.INCLUDE_ALL) {
|
||||
List<@NonNull ConfigDescriptionParameter> configDescriptions = rule.getConfigurationDescriptions();
|
||||
if (!configDescriptions.isEmpty()) {
|
||||
Map<String, YamlConfigDescriptionParameterDTO> configDescriptionDtos = new LinkedHashMap<>(
|
||||
configDescriptions.size());
|
||||
for (ConfigDescriptionParameter parameter : configDescriptions) {
|
||||
configDescriptionDtos.put(parameter.getName(),
|
||||
new YamlConfigDescriptionParameterDTO(parameter, true));
|
||||
}
|
||||
this.configDescriptions = configDescriptionDtos;
|
||||
}
|
||||
this.actions = actionDtos;
|
||||
}
|
||||
List<@NonNull Condition> conditions = rule.getConditions();
|
||||
if (!conditions.isEmpty()) {
|
||||
List<YamlConditionDTO> conditionsDtos = new ArrayList<>(conditions.size());
|
||||
for (Condition condition : conditions) {
|
||||
conditionsDtos.add(new YamlConditionDTO(condition));
|
||||
if (option != RuleSerializationOption.STUB_ONLY) {
|
||||
List<@NonNull Action> actions = rule.getActions();
|
||||
if (!actions.isEmpty()) {
|
||||
List<YamlActionDTO> actionDtos = new ArrayList<>(actions.size());
|
||||
for (Action action : actions) {
|
||||
actionDtos.add(new YamlActionDTO(action, option));
|
||||
}
|
||||
this.actions = actionDtos;
|
||||
}
|
||||
this.conditions = conditionsDtos;
|
||||
}
|
||||
List<@NonNull Trigger> triggers = rule.getTriggers();
|
||||
if (!triggers.isEmpty()) {
|
||||
List<YamlModuleDTO> triggerDtos = new ArrayList<>(triggers.size());
|
||||
for (Trigger trigger : triggers) {
|
||||
triggerDtos.add(new YamlModuleDTO(trigger));
|
||||
List<@NonNull Condition> conditions = rule.getConditions();
|
||||
if (!conditions.isEmpty()) {
|
||||
List<YamlConditionDTO> conditionsDtos = new ArrayList<>(conditions.size());
|
||||
for (Condition condition : conditions) {
|
||||
conditionsDtos.add(new YamlConditionDTO(condition, option));
|
||||
}
|
||||
this.conditions = conditionsDtos;
|
||||
}
|
||||
List<@NonNull Trigger> triggers = rule.getTriggers();
|
||||
if (!triggers.isEmpty()) {
|
||||
List<YamlModuleDTO> triggerDtos = new ArrayList<>(triggers.size());
|
||||
for (Trigger trigger : triggers) {
|
||||
triggerDtos.add(new YamlModuleDTO(trigger, option));
|
||||
}
|
||||
this.triggers = triggerDtos;
|
||||
}
|
||||
this.triggers = triggerDtos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +167,10 @@ public class YamlRuleDTO implements ModularDTO<YamlRuleDTO, ObjectMapper, JsonNo
|
||||
try {
|
||||
partial = mapper.treeToValue(node, YamlPartialRuleDTO.class);
|
||||
result.uid = partial.uid;
|
||||
result.template = partial.template;
|
||||
result.templateState = TemplateState.typeOf(partial.templateState);
|
||||
String templateUID = partial.template;
|
||||
result.template = templateUID;
|
||||
result.templateState = templateUID == null || templateUID.isBlank() ? TemplateState.NO_TEMPLATE
|
||||
: TemplateState.PENDING;
|
||||
result.label = partial.label;
|
||||
result.tags = partial.tags;
|
||||
result.description = partial.description;
|
||||
@@ -409,6 +439,7 @@ public class YamlRuleDTO implements ModularDTO<YamlRuleDTO, ObjectMapper, JsonNo
|
||||
@JsonAlias({ "templateUid", "templateUID" })
|
||||
public String template;
|
||||
public String templateState;
|
||||
@JsonAlias({ "name" })
|
||||
public String label;
|
||||
public Set<@NonNull String> tags;
|
||||
public String description;
|
||||
|
||||
+14
-7
@@ -29,6 +29,7 @@ import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.converter.RuleTemplateSerializer.RuleTemplateSerializationOption;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.common.AbstractUID;
|
||||
import org.openhab.core.config.core.ConfigDescriptionParameter;
|
||||
@@ -75,19 +76,25 @@ public class YamlRuleTemplateDTO
|
||||
* Creates a new instance based on the specified {@link RuleTemplate}.
|
||||
*
|
||||
* @param template the {@link RuleTemplate}.
|
||||
* @param option the {@link RuleTemplateSerializationOption} that decides how the to serialize the
|
||||
* {@link RuleTemplate}.
|
||||
*/
|
||||
public YamlRuleTemplateDTO(@NonNull RuleTemplate template) {
|
||||
public YamlRuleTemplateDTO(@NonNull RuleTemplate template, RuleTemplateSerializationOption option) {
|
||||
this.uid = template.getUID();
|
||||
this.label = template.getLabel();
|
||||
this.tags = template.getTags();
|
||||
Set<@NonNull String> tags = template.getTags();
|
||||
this.tags = option != RuleTemplateSerializationOption.INCLUDE_ALL && tags.isEmpty() ? null : tags;
|
||||
this.description = template.getDescription();
|
||||
this.visibility = template.getVisibility();
|
||||
this.visibility = option == RuleTemplateSerializationOption.INCLUDE_ALL
|
||||
|| template.getVisibility() != Visibility.VISIBLE ? template.getVisibility() : null;
|
||||
|
||||
List<@NonNull ConfigDescriptionParameter> configDescriptions = template.getConfigurationDescriptions();
|
||||
if (!configDescriptions.isEmpty()) {
|
||||
Map<String, YamlConfigDescriptionParameterDTO> configDescriptionDtos = new LinkedHashMap<>(
|
||||
configDescriptions.size());
|
||||
for (ConfigDescriptionParameter parameter : configDescriptions) {
|
||||
configDescriptionDtos.put(parameter.getName(), new YamlConfigDescriptionParameterDTO(parameter));
|
||||
configDescriptionDtos.put(parameter.getName(), new YamlConfigDescriptionParameterDTO(parameter,
|
||||
option == RuleTemplateSerializationOption.INCLUDE_ALL));
|
||||
}
|
||||
this.configDescriptions = configDescriptionDtos;
|
||||
}
|
||||
@@ -95,7 +102,7 @@ public class YamlRuleTemplateDTO
|
||||
if (!actions.isEmpty()) {
|
||||
List<YamlActionDTO> actionDtos = new ArrayList<>(actions.size());
|
||||
for (Action action : actions) {
|
||||
actionDtos.add(new YamlActionDTO(action));
|
||||
actionDtos.add(new YamlActionDTO(action, option.toRuleSerializationOption()));
|
||||
}
|
||||
this.actions = actionDtos;
|
||||
}
|
||||
@@ -103,7 +110,7 @@ public class YamlRuleTemplateDTO
|
||||
if (!conditions.isEmpty()) {
|
||||
List<YamlConditionDTO> conditionsDtos = new ArrayList<>(conditions.size());
|
||||
for (Condition condition : conditions) {
|
||||
conditionsDtos.add(new YamlConditionDTO(condition));
|
||||
conditionsDtos.add(new YamlConditionDTO(condition, option.toRuleSerializationOption()));
|
||||
}
|
||||
this.conditions = conditionsDtos;
|
||||
}
|
||||
@@ -111,7 +118,7 @@ public class YamlRuleTemplateDTO
|
||||
if (!triggers.isEmpty()) {
|
||||
List<YamlModuleDTO> triggerDtos = new ArrayList<>(triggers.size());
|
||||
for (Trigger trigger : triggers) {
|
||||
triggerDtos.add(new YamlModuleDTO(trigger));
|
||||
triggerDtos.add(new YamlModuleDTO(trigger, option.toRuleSerializationOption()));
|
||||
}
|
||||
this.triggers = triggerDtos;
|
||||
}
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.rules.converter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Module;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.converter.RuleParser;
|
||||
import org.openhab.core.automation.converter.RuleSerializer;
|
||||
import org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRule;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
import org.openhab.core.model.yaml.YamlModelRepository;
|
||||
import org.openhab.core.model.yaml.internal.rules.YamlRuleDTO;
|
||||
import org.openhab.core.model.yaml.internal.rules.YamlRuleProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* {@link YamlRuleConverter} is the YAML converter for {@link Rule} objects.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { RuleSerializer.class, RuleParser.class })
|
||||
public class YamlRuleConverter implements RuleSerializer, RuleParser {
|
||||
|
||||
private final YamlModelRepository modelRepository;
|
||||
private final YamlRuleProvider ruleProvider;
|
||||
|
||||
@Activate
|
||||
public YamlRuleConverter(@Reference YamlModelRepository modelRepository, @Reference YamlRuleProvider ruleProvider) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.ruleProvider = ruleProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGeneratedFormat() {
|
||||
return "YAML";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SerializabilityResult<String>> checkSerializability(Collection<Rule> rules) {
|
||||
List<SerializabilityResult<String>> result = new ArrayList<>(rules.size());
|
||||
boolean failed;
|
||||
for (Rule rule : rules) {
|
||||
failed = false;
|
||||
if (rule instanceof SimpleRule) {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), false,
|
||||
"Rule '" + rule.getUID() + "' is a SimpleRule with an inaccessible action."));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Action action : rule.getActions()) {
|
||||
if (action.getConfiguration().get("type") instanceof String type
|
||||
&& "application/vnd.openhab.dsl.rule".equals(type)
|
||||
&& action.getConfiguration().get(Module.SHARED_CONTEXT) instanceof Boolean shared
|
||||
&& shared.booleanValue()) {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), false,
|
||||
"Rule '" + rule.getUID() + "': action '" + action.getId() + "' has shared context."));
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
result.add(new SerializabilityResult<>(rule.getUID(), true, ""));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRulesToBeSerialized(String id, List<Rule> rules, RuleSerializationOption option)
|
||||
throws SerializationException {
|
||||
List<String> errors = null;
|
||||
List<SerializabilityResult<String>> checks = checkSerializability(rules);
|
||||
for (SerializabilityResult<String> check : checks) {
|
||||
if (!check.ok()) {
|
||||
if (errors == null) {
|
||||
errors = new ArrayList<>();
|
||||
}
|
||||
errors.add(check.failureReason());
|
||||
}
|
||||
}
|
||||
if (errors != null) {
|
||||
throw new SerializationException(
|
||||
"Rule serialization attempt failed with:\n " + String.join("\n ", errors));
|
||||
}
|
||||
|
||||
Set<Rule> handledRules = new HashSet<>();
|
||||
List<YamlElement> elements = new ArrayList<>(rules.size());
|
||||
for (Rule rule : rules) {
|
||||
if (handledRules.contains(rule)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
elements.add(new YamlRuleDTO(rule, option));
|
||||
} catch (RuntimeException e) {
|
||||
if (errors == null) {
|
||||
errors = new ArrayList<>();
|
||||
}
|
||||
errors.add("Rule '" + rule.getUID() + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (errors != null) {
|
||||
throw new SerializationException(
|
||||
"Rule serialization attempt failed with:\n " + String.join("\n ", errors));
|
||||
}
|
||||
|
||||
modelRepository.addElementsToBeGenerated(id, elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateFormat(String id, OutputStream out) {
|
||||
modelRepository.generateFileFormat(id, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParserFormat() {
|
||||
return "YAML";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String startParsingFormat(String syntax, List<String> errors, List<String> warnings) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(syntax.getBytes(StandardCharsets.UTF_8));
|
||||
return modelRepository.createIsolatedModel(inputStream, errors, warnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Rule> getParsedObjects(String modelName) {
|
||||
return ruleProvider.getAllFromModel(modelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishParsingFormat(String modelName) {
|
||||
modelRepository.removeIsolatedModel(modelName);
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.rules.converter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.converter.RuleTemplateParser;
|
||||
import org.openhab.core.automation.converter.RuleTemplateSerializer;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.converter.SerializabilityResult;
|
||||
import org.openhab.core.io.dto.SerializationException;
|
||||
import org.openhab.core.model.yaml.YamlElement;
|
||||
import org.openhab.core.model.yaml.YamlModelRepository;
|
||||
import org.openhab.core.model.yaml.internal.rules.YamlRuleTemplateDTO;
|
||||
import org.openhab.core.model.yaml.internal.rules.YamlRuleTemplateProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* {@link YamlRuleTemplateConverter} is the YAML converter for {@link RuleTemplate} objects.
|
||||
*
|
||||
* @author Ravi Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { RuleTemplateSerializer.class, RuleTemplateParser.class })
|
||||
public class YamlRuleTemplateConverter implements RuleTemplateSerializer, RuleTemplateParser {
|
||||
|
||||
private final YamlModelRepository modelRepository;
|
||||
private final YamlRuleTemplateProvider templateProvider;
|
||||
|
||||
@Activate
|
||||
public YamlRuleTemplateConverter(@Reference YamlModelRepository modelRepository,
|
||||
@Reference YamlRuleTemplateProvider templateProvider) {
|
||||
this.modelRepository = modelRepository;
|
||||
this.templateProvider = templateProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGeneratedFormat() {
|
||||
return "YAML";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SerializabilityResult<String>> checkSerializability(Collection<RuleTemplate> templates) {
|
||||
List<SerializabilityResult<String>> result = new ArrayList<>(templates.size());
|
||||
for (RuleTemplate template : templates) {
|
||||
// There are no known circumstances under which a rule template can't be serialized to YAML
|
||||
result.add(new SerializabilityResult<>(template.getUID(), true, ""));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTemplatesToBeSerialized(String id, List<RuleTemplate> templates,
|
||||
RuleTemplateSerializationOption option) throws SerializationException {
|
||||
List<String> errors = null;
|
||||
List<SerializabilityResult<String>> checks = checkSerializability(templates);
|
||||
for (SerializabilityResult<String> check : checks) {
|
||||
if (!check.ok()) {
|
||||
if (errors == null) {
|
||||
errors = new ArrayList<>();
|
||||
}
|
||||
errors.add(check.failureReason());
|
||||
}
|
||||
}
|
||||
if (errors != null) {
|
||||
throw new SerializationException(
|
||||
"Rule template serialization attempt failed with:\n " + String.join("\n ", errors));
|
||||
}
|
||||
|
||||
Set<RuleTemplate> handledTemplates = new HashSet<>();
|
||||
List<YamlElement> elements = new ArrayList<>(templates.size());
|
||||
for (RuleTemplate template : templates) {
|
||||
if (handledTemplates.contains(template)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
elements.add(new YamlRuleTemplateDTO(template, option));
|
||||
} catch (RuntimeException e) {
|
||||
if (errors == null) {
|
||||
errors = new ArrayList<>();
|
||||
}
|
||||
errors.add("Rule template '" + template.getUID() + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (errors != null) {
|
||||
throw new SerializationException(
|
||||
"Rule template serialization attempt failed with:\n " + String.join("\n ", errors));
|
||||
}
|
||||
|
||||
modelRepository.addElementsToBeGenerated(id, elements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateFormat(String id, OutputStream out) {
|
||||
modelRepository.generateFileFormat(id, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParserFormat() {
|
||||
return "YAML";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String startParsingFormat(String syntax, List<String> errors, List<String> warnings) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(syntax.getBytes(StandardCharsets.UTF_8));
|
||||
return modelRepository.createIsolatedModel(inputStream, errors, warnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RuleTemplate> getParsedObjects(String modelName) {
|
||||
return templateProvider.getAllFromModel(modelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishParsingFormat(String modelName) {
|
||||
modelRepository.removeIsolatedModel(modelName);
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -56,12 +56,11 @@ public class YamlActionDTOTest {
|
||||
Action a = ActionBuilder.create().withId("action1").withTypeUID("type1").build();
|
||||
YamlActionDTO action1 = new YamlActionDTO(a);
|
||||
YamlActionDTO action2 = new YamlActionDTO();
|
||||
assertEquals("YamlActionDTO [inputs={}, id=action1, type=type1, config={}]", action1.toString());
|
||||
assertEquals("YamlActionDTO [id=action1, type=type1]", action1.toString());
|
||||
assertEquals("YamlActionDTO []", action2.toString());
|
||||
action1.label = "Label1";
|
||||
action1.description = "Description1";
|
||||
assertEquals(
|
||||
"YamlActionDTO [inputs={}, id=action1, type=type1, label=Label1, description=Description1, config={}]",
|
||||
assertEquals("YamlActionDTO [id=action1, type=type1, label=Label1, description=Description1]",
|
||||
action1.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -56,12 +56,11 @@ public class YamlConditionDTOTest {
|
||||
Condition c = ConditionBuilder.create().withId("condition1").withTypeUID("type1").build();
|
||||
YamlConditionDTO condition1 = new YamlConditionDTO(c);
|
||||
YamlConditionDTO condition2 = new YamlConditionDTO();
|
||||
assertEquals("YamlConditionDTO [inputs={}, id=condition1, type=type1, config={}]", condition1.toString());
|
||||
assertEquals("YamlConditionDTO [id=condition1, type=type1]", condition1.toString());
|
||||
assertEquals("YamlConditionDTO []", condition2.toString());
|
||||
condition1.label = "Label1";
|
||||
condition1.description = "Description1";
|
||||
assertEquals(
|
||||
"YamlConditionDTO [inputs={}, id=condition1, type=type1, label=Label1, description=Description1, config={}]",
|
||||
assertEquals("YamlConditionDTO [id=condition1, type=type1, label=Label1, description=Description1]",
|
||||
condition1.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -56,11 +56,11 @@ public class YamlModuleDTOTest {
|
||||
Trigger t = TriggerBuilder.create().withId("trigger1").withTypeUID("type1").build();
|
||||
YamlModuleDTO trigger1 = new YamlModuleDTO(t);
|
||||
YamlModuleDTO trigger2 = new YamlModuleDTO();
|
||||
assertEquals("YamlModuleDTO [id=trigger1, type=type1, config={}]", trigger1.toString());
|
||||
assertEquals("YamlModuleDTO [id=trigger1, type=type1]", trigger1.toString());
|
||||
assertEquals("YamlModuleDTO []", trigger2.toString());
|
||||
trigger1.label = "Label1";
|
||||
trigger1.description = "Description1";
|
||||
assertEquals("YamlModuleDTO [id=trigger1, type=type1, label=Label1, description=Description1, config={}]",
|
||||
assertEquals("YamlModuleDTO [id=trigger1, type=type1, label=Label1, description=Description1]",
|
||||
trigger1.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+19
-4
@@ -29,6 +29,7 @@ import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.Rule;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.converter.RuleSerializer.RuleSerializationOption;
|
||||
import org.openhab.core.automation.util.ActionBuilder;
|
||||
import org.openhab.core.automation.util.ConditionBuilder;
|
||||
import org.openhab.core.automation.util.RuleBuilder;
|
||||
@@ -61,18 +62,32 @@ public class YamlRuleDTOTest {
|
||||
|
||||
rule = RuleBuilder.create(rule).withConfigurationDescriptions(
|
||||
List.of(ConfigDescriptionParameterBuilder.create("number", Type.DECIMAL).build())).build();
|
||||
YamlRuleDTO ruleDTO = new YamlRuleDTO(rule);
|
||||
YamlRuleDTO ruleDTO = new YamlRuleDTO(rule, RuleSerializationOption.INCLUDE_ALL);
|
||||
assertNotNull(ruleDTO);
|
||||
assertEquals(
|
||||
"YamlRuleDTO [uid=rule1, templateState=no-template, label=Rule 1, tags=[], visibility=VISIBLE, config={}, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [inputs={}, id=condition1, type=type1, config={}]], actions=[YamlActionDTO [inputs={}, id=action1, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
"YamlRuleDTO [uid=rule1, templateState=no-template, label=Rule 1, tags=[], visibility=VISIBLE, config={}, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [id=condition1, inputs={}, type=type1, config={}]], actions=[YamlActionDTO [id=action1, inputs={}, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
ruleDTO.toString());
|
||||
|
||||
rule = RuleBuilder.create(rule).withTemplateUID("templateUID").withActions(List.of())
|
||||
.withDescription("Rule description").build();
|
||||
ruleDTO = new YamlRuleDTO(rule);
|
||||
ruleDTO = new YamlRuleDTO(rule, RuleSerializationOption.INCLUDE_ALL);
|
||||
assertNotNull(ruleDTO);
|
||||
assertEquals(
|
||||
"YamlRuleDTO [uid=rule1, template=templateUID, templateState=no-template, label=Rule 1, tags=[], description=Rule description, visibility=VISIBLE, config={}, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [inputs={}, id=condition1, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
"YamlRuleDTO [uid=rule1, template=templateUID, templateState=no-template, label=Rule 1, tags=[], description=Rule description, visibility=VISIBLE, config={}, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [id=condition1, inputs={}, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
ruleDTO.toString());
|
||||
ruleDTO = new YamlRuleDTO(rule, RuleSerializationOption.NORMAL);
|
||||
assertNotNull(ruleDTO);
|
||||
assertEquals(
|
||||
"YamlRuleDTO [uid=rule1, template=templateUID, label=Rule 1, description=Rule description, conditions=[YamlConditionDTO [id=condition1, type=type1]], triggers=[YamlModuleDTO [id=trigger1, type=type1]]]",
|
||||
ruleDTO.toString());
|
||||
ruleDTO = new YamlRuleDTO(rule, RuleSerializationOption.STRIP_TEMPLATE);
|
||||
assertNotNull(ruleDTO);
|
||||
assertEquals(
|
||||
"YamlRuleDTO [uid=rule1, label=Rule 1, description=Rule description, conditions=[YamlConditionDTO [id=condition1, type=type1]], triggers=[YamlModuleDTO [id=trigger1, type=type1]]]",
|
||||
ruleDTO.toString());
|
||||
ruleDTO = new YamlRuleDTO(rule, RuleSerializationOption.STUB_ONLY);
|
||||
assertNotNull(ruleDTO);
|
||||
assertEquals("YamlRuleDTO [uid=rule1, template=templateUID, label=Rule 1, description=Rule description, ]",
|
||||
ruleDTO.toString());
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -217,7 +217,7 @@ public class YamlRuleProviderTest {
|
||||
assertThat(rule.getName(), is("Mode TV"));
|
||||
assertThat(rule.getDescription(), is(emptyOrNullString()));
|
||||
assertThat(rule.getTemplateUID(), is(emptyOrNullString()));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.INSTANTIATED));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.NO_TEMPLATE));
|
||||
assertThat(rule.getVisibility(), is(Visibility.VISIBLE));
|
||||
Configuration config = rule.getConfiguration();
|
||||
assertThat(config.getProperties(), is(aMapWithSize(1)));
|
||||
@@ -277,7 +277,7 @@ public class YamlRuleProviderTest {
|
||||
assertThat(rule.getDescription(),
|
||||
is("Creates timers to transition a state Item to a new state at defined times of day."));
|
||||
assertThat(rule.getTemplateUID(), is("none"));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.TEMPLATE_MISSING));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.PENDING));
|
||||
assertThat(rule.getVisibility(), is(Visibility.VISIBLE));
|
||||
config = rule.getConfiguration();
|
||||
assertThat(config.getProperties(), is(aMapWithSize(3)));
|
||||
@@ -366,7 +366,7 @@ public class YamlRuleProviderTest {
|
||||
assertThat(rule.getDescription(), is(
|
||||
"This will monitor the power consumption of a washing machine and send an alert command when it gets below a threshold, meaning it has finished."));
|
||||
assertThat(rule.getTemplateUID(), is(emptyOrNullString()));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.TEMPLATE_MISSING));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.NO_TEMPLATE));
|
||||
assertThat(rule.getVisibility(), is(Visibility.HIDDEN));
|
||||
assertThat(rule.getConfiguration().getProperties(), is(anEmptyMap()));
|
||||
configDescriptions = rule.getConfigurationDescriptions();
|
||||
@@ -451,7 +451,7 @@ public class YamlRuleProviderTest {
|
||||
assertThat(rule.getName(), is("Full Rule"));
|
||||
assertThat(rule.getDescription(), is("The description of the full rule"));
|
||||
assertThat(rule.getTemplateUID(), is("template:non-existing"));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.TEMPLATE_MISSING));
|
||||
assertThat(rule.getTemplateState(), is(TemplateState.PENDING));
|
||||
assertThat(rule.getVisibility(), is(Visibility.EXPERT));
|
||||
Configuration config = rule.getConfiguration();
|
||||
assertThat(config.getProperties(), is(aMapWithSize(4)));
|
||||
|
||||
+12
-5
@@ -28,6 +28,7 @@ import org.openhab.core.automation.Action;
|
||||
import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.Trigger;
|
||||
import org.openhab.core.automation.Visibility;
|
||||
import org.openhab.core.automation.converter.RuleTemplateSerializer.RuleTemplateSerializationOption;
|
||||
import org.openhab.core.automation.template.RuleTemplate;
|
||||
import org.openhab.core.automation.util.ActionBuilder;
|
||||
import org.openhab.core.automation.util.ConditionBuilder;
|
||||
@@ -49,25 +50,31 @@ public class YamlRuleTemplateDTOTest {
|
||||
Action action = ActionBuilder.create().withId("action1").withTypeUID("type1").build();
|
||||
RuleTemplate template = new RuleTemplate("template1", "Foo Template", "Foo rule template", Set.of("test"),
|
||||
List.of(), List.of(), List.of(action), List.of(), Visibility.VISIBLE);
|
||||
assertNotNull(new YamlRuleTemplateDTO(template));
|
||||
assertNotNull(new YamlRuleTemplateDTO(template, RuleTemplateSerializationOption.NORMAL));
|
||||
|
||||
Condition condition = ConditionBuilder.create().withId("condition1").withTypeUID("type1").build();
|
||||
template = new RuleTemplate("template1", "Foo Template", "Foo rule template", Set.of("test"), List.of(),
|
||||
List.of(condition), List.of(action), List.of(), Visibility.VISIBLE);
|
||||
assertNotNull(new YamlRuleTemplateDTO(template));
|
||||
assertNotNull(new YamlRuleTemplateDTO(template, RuleTemplateSerializationOption.NORMAL));
|
||||
|
||||
Trigger trigger = TriggerBuilder.create().withId("trigger1").withTypeUID("type1").build();
|
||||
template = new RuleTemplate("template1", "Foo Template", "Foo rule template", Set.of("test"), List.of(trigger),
|
||||
List.of(condition), List.of(action), List.of(), Visibility.VISIBLE);
|
||||
assertNotNull(new YamlRuleTemplateDTO(template));
|
||||
assertNotNull(new YamlRuleTemplateDTO(template, RuleTemplateSerializationOption.NORMAL));
|
||||
|
||||
template = new RuleTemplate("template1", "Foo Template", "Foo rule template", Set.of("test"), List.of(trigger),
|
||||
List.of(condition), List.of(action),
|
||||
List.of(ConfigDescriptionParameterBuilder.create("number", Type.DECIMAL).build()), Visibility.VISIBLE);
|
||||
YamlRuleTemplateDTO templateDTO = new YamlRuleTemplateDTO(template);
|
||||
YamlRuleTemplateDTO templateDTO = new YamlRuleTemplateDTO(template,
|
||||
RuleTemplateSerializationOption.INCLUDE_ALL);
|
||||
assertNotNull(templateDTO);
|
||||
assertEquals(
|
||||
"YamlRuleTemplateDTO [uid=template1, label=Foo Template, tags=[test], description=Foo rule template, visibility=VISIBLE, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [inputs={}, id=condition1, type=type1, config={}]], actions=[YamlActionDTO [inputs={}, id=action1, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
"YamlRuleTemplateDTO [uid=template1, label=Foo Template, tags=[test], description=Foo rule template, visibility=VISIBLE, configDescriptions={number=YamlConfigDescriptionParameterDTO [required=false, type=DECIMAL, readOnly=false, multiple=false, advanced=false, verify=false, limitToOptions=true, ]}, conditions=[YamlConditionDTO [id=condition1, inputs={}, type=type1, config={}]], actions=[YamlActionDTO [id=action1, inputs={}, type=type1, config={}]], triggers=[YamlModuleDTO [id=trigger1, type=type1, config={}]]]",
|
||||
templateDTO.toString());
|
||||
templateDTO = new YamlRuleTemplateDTO(template, RuleTemplateSerializationOption.NORMAL);
|
||||
assertNotNull(templateDTO);
|
||||
assertEquals(
|
||||
"YamlRuleTemplateDTO [uid=template1, label=Foo Template, tags=[test], description=Foo rule template, configDescriptions={number=YamlConfigDescriptionParameterDTO [type=DECIMAL, ]}, conditions=[YamlConditionDTO [id=condition1, type=type1]], actions=[YamlActionDTO [id=action1, type=type1]], triggers=[YamlModuleDTO [id=trigger1, type=type1]]]",
|
||||
templateDTO.toString());
|
||||
}
|
||||
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 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.converter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* A container that holds the result of a serializability check.
|
||||
*
|
||||
* @author Nadahar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record SerializabilityResult<T> (T uid, boolean ok, String failureReason) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName()).append(" [uid=").append(uid).append(", ok=").append(ok);
|
||||
if (!ok) {
|
||||
sb.append(", failureReason=").append(failureReason);
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -429,6 +429,7 @@
|
||||
|
||||
<feature name="openhab-core-model-yaml" version="${project.version}">
|
||||
<feature>openhab-core-base</feature>
|
||||
<feature>openhab-core-automation-module-script-rulesupport</feature>
|
||||
<feature>openhab-core-ui</feature>
|
||||
<bundle>mvn:org.openhab.core.bundles/org.openhab.core.model.yaml/${project.version}</bundle>
|
||||
<requirement>openhab.tp;filter:="(feature=jackson)"</requirement>
|
||||
|
||||
Reference in New Issue
Block a user