Regenerate rules from templates (#4718)

* Rule template regeneration support

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
pull/4829/head
Nadahar 2025-05-26 22:53:17 +02:00 committed by GitHub
parent fb62bf33cd
commit 5b12280f5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 332 additions and 134 deletions

View File

@ -118,4 +118,9 @@ public class RuleSupportRuleRegistryDelegate implements RuleRegistry {
public Collection<Rule> getByTags(String... tags) { public Collection<Rule> getByTags(String... tags) {
return ruleRegistry.getByTags(tags); return ruleRegistry.getByTags(tags);
} }
@Override
public void regenerateFromTemplate(String ruleUID) {
ruleRegistry.regenerateFromTemplate(ruleUID);
}
} }

View File

@ -2,14 +2,14 @@
"conditions": [ "conditions": [
{ {
"uid": "jsr223.ScriptedCondition", "uid": "jsr223.ScriptedCondition",
"label": "Scripted condition", "label": "Opaque Condition",
"description": "allows the definition of a condition by a script", "description": "Evaluates a condition using external code. See the rule source for details.",
"visibility": "EXPERT", "visibility": "EXPERT",
"configDescriptions": [ "configDescriptions": [
{ {
"name": "privId", "name": "privId",
"type": "TEXT", "type": "TEXT",
"description": "the identifier of the private method", "description": "The identifier of the private method",
"required": true "required": true
} }
] ]
@ -18,14 +18,14 @@
"actions": [ "actions": [
{ {
"uid": "jsr223.ScriptedAction", "uid": "jsr223.ScriptedAction",
"label": "Scripted action", "label": "Opaque Action",
"description": "allows the execution of a method defined by a script", "description": "Executes external code. See the rule source for details.",
"visibility": "EXPERT", "visibility": "EXPERT",
"configDescriptions": [ "configDescriptions": [
{ {
"name": "privId", "name": "privId",
"type": "TEXT", "type": "TEXT",
"description": "the identifier of the private method", "description": "The identifier of the private method",
"required": true "required": true
} }
], ],
@ -33,8 +33,8 @@
{ {
"name": "result", "name": "result",
"type": "java.lang.Object", "type": "java.lang.Object",
"label": "result", "label": "Result",
"description": "the script result.", "description": "The script result",
"reference": "" "reference": ""
} }
] ]
@ -43,14 +43,14 @@
"triggers": [ "triggers": [
{ {
"uid": "jsr223.ScriptedTrigger", "uid": "jsr223.ScriptedTrigger",
"label": "Scripted trigger", "label": "Opaque Trigger",
"description": "allows the execution of a method defined by a script", "description": "A trigger controlled by external code. See the rule source for details.",
"visibility": "EXPERT", "visibility": "EXPERT",
"configDescriptions": [ "configDescriptions": [
{ {
"name": "privId", "name": "privId",
"type": "TEXT", "type": "TEXT",
"description": "the identifier of the private method", "description": "The identifier of the private method",
"required": true "required": true
} }
], ],
@ -58,8 +58,8 @@
{ {
"name": "triggerOutput", "name": "triggerOutput",
"type": "java.lang.String", "type": "java.lang.String",
"label": "TriggerOutput label", "label": "Trigger Output",
"description": "Text from the trigger", "description": "The text from the trigger",
"reference": "consoleInput", "reference": "consoleInput",
"defaultValue": "dtag" "defaultValue": "dtag"
} }

View File

@ -1,21 +1,21 @@
# jsr223.ScriptedAction # jsr223.ScriptedAction
module-type.jsr223.ScriptedAction.label = Scripted action module-type.jsr223.ScriptedAction.label = Opaque Action
module-type.jsr223.ScriptedAction.description = allows the execution of a method defined by a script module-type.jsr223.ScriptedAction.description = Executes external code. See the rule source for details.
module-type.jsr223.ScriptedAction.config.privId.description = the identifier of the private method module-type.jsr223.ScriptedAction.config.privId.description = The identifier of the private method
module-type.jsr223.ScriptedAction.output.result.label = result module-type.jsr223.ScriptedAction.output.result.label = Result
module-type.jsr223.ScriptedAction.output.result.description = the script result. module-type.jsr223.ScriptedAction.output.result.description = The script result
# jsr223.ScriptedCondition # jsr223.ScriptedCondition
module-type.jsr223.ScriptedCondition.label = Scripted condition module-type.jsr223.ScriptedCondition.label = Opaque Condition
module-type.jsr223.ScriptedCondition.description = allows the definition of a condition by a script module-type.jsr223.ScriptedCondition.description = Evaluates a condition using external code. See the rule source for details.
module-type.jsr223.ScriptedCondition.config.privId.description = the identifier of the private method module-type.jsr223.ScriptedCondition.config.privId.description = The identifier of the private method
# jsr223.ScriptedTrigger # jsr223.ScriptedTrigger
module-type.jsr223.ScriptedTrigger.label = Scripted trigger module-type.jsr223.ScriptedTrigger.label = Opaque Trigger
module-type.jsr223.ScriptedTrigger.description = allows the execution of a method defined by a script module-type.jsr223.ScriptedTrigger.description = A trigger controlled by external code. See the rule source for details.
module-type.jsr223.ScriptedTrigger.config.privId.description = the identifier of the private method module-type.jsr223.ScriptedTrigger.config.privId.description = The identifier of the private method
module-type.jsr223.ScriptedTrigger.output.triggerOutput.label = TriggerOutput label module-type.jsr223.ScriptedTrigger.output.triggerOutput.label = Trigger Output
module-type.jsr223.ScriptedTrigger.output.triggerOutput.description = Text from the trigger module-type.jsr223.ScriptedTrigger.output.triggerOutput.description = The text from the trigger

View File

@ -84,13 +84,13 @@ public class ScriptModuleTypeProvider extends AbstractProvider<ModuleType> imple
List<Output> outputs = new ArrayList<>(); List<Output> outputs = new ArrayList<>();
Output result = new Output("result", "java.lang.Object", "result", "the script result", null, null, null); Output result = new Output("result", "java.lang.Object", "result", "the script result", null, null, null);
outputs.add(result); outputs.add(result);
return new ActionType(ScriptActionHandler.TYPE_ID, getConfigDescriptions(locale), "execute an inline script", return new ActionType(ScriptActionHandler.TYPE_ID, getConfigDescriptions(locale), "Execute an inline script",
"Allows the execution of a user-defined script.", null, Visibility.VISIBLE, null, outputs); "Executes a user-defined script", null, Visibility.VISIBLE, null, outputs);
} }
private ModuleType getScriptConditionType(@Nullable Locale locale) { private ModuleType getScriptConditionType(@Nullable Locale locale) {
return new ConditionType(ScriptConditionHandler.TYPE_ID, getConfigDescriptions(locale), return new ConditionType(ScriptConditionHandler.TYPE_ID, getConfigDescriptions(locale),
"an inline script evaluates to true", "Allows the definition of a condition through a script.", null, "An inline script evaluates to true", "Allows the definition of a condition through a script", null,
Visibility.VISIBLE, null); Visibility.VISIBLE, null);
} }
@ -111,11 +111,11 @@ public class ScriptModuleTypeProvider extends AbstractProvider<ModuleType> imple
} }
final ConfigDescriptionParameter scriptType = ConfigDescriptionParameterBuilder.create("type", Type.TEXT) final ConfigDescriptionParameter scriptType = ConfigDescriptionParameterBuilder.create("type", Type.TEXT)
.withRequired(true).withMultiple(false).withLabel("Script Type") .withRequired(true).withMultiple(false).withLabel("Script Type")
.withDescription("the scripting language used").withOptions(parameterOptionsList) .withDescription("The scripting language used").withOptions(parameterOptionsList)
.withLimitToOptions(true).build(); .withLimitToOptions(true).build();
final ConfigDescriptionParameter script = ConfigDescriptionParameterBuilder.create("script", Type.TEXT) final ConfigDescriptionParameter script = ConfigDescriptionParameterBuilder.create("script", Type.TEXT)
.withRequired(true).withReadOnly(false).withMultiple(false).withLabel("Script").withContext("script") .withRequired(true).withReadOnly(false).withMultiple(false).withLabel("Script").withContext("script")
.withDescription("the script to execute").build(); .withDescription("The script to execute").build();
return List.of(scriptType, script); return List.of(scriptType, script);
} }

View File

@ -13,8 +13,10 @@
package org.openhab.core.automation.rest.internal; package org.openhab.core.automation.rest.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam; import javax.ws.rs.HeaderParam;
@ -100,21 +102,43 @@ public class ModuleTypeResource implements RESTResource {
public Response getAll( public Response getAll(
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language,
@QueryParam("tags") @Parameter(description = "tags for filtering") @Nullable String tagList, @QueryParam("tags") @Parameter(description = "tags for filtering") @Nullable String tagList,
@QueryParam("type") @Parameter(description = "filtering by action, condition or trigger") @Nullable String type) { @QueryParam("type") @Parameter(description = "filtering by action, condition or trigger") @Nullable String type,
@QueryParam("asMap") @Parameter(description = "returns an object of arrays by type instead of a mixed array") @Nullable Boolean asMap) {
final Locale locale = localeService.getLocale(language); final Locale locale = localeService.getLocale(language);
final String[] tags = tagList != null ? tagList.split(",") : new String[0]; final String[] tags = tagList != null ? tagList.split(",") : new String[0];
final List<ModuleTypeDTO> modules = new ArrayList<>(); Map<String, List<ModuleTypeDTO>> modulesMap = null;
List<ModuleTypeDTO> modules = null;
if (asMap == null || !asMap.booleanValue()) {
modules = new ArrayList<>();
} else {
modulesMap = new LinkedHashMap<>();
}
if (type == null || "trigger".equals(type)) { if (type == null || "trigger".equals(type)) {
modules.addAll(TriggerTypeDTOMapper.map(moduleTypeRegistry.getTriggers(locale, tags))); if (modules != null) {
modules.addAll(TriggerTypeDTOMapper.map(moduleTypeRegistry.getTriggers(locale, tags)));
} else if (modulesMap != null) {
modulesMap.put("triggers", new ArrayList<ModuleTypeDTO>(
TriggerTypeDTOMapper.map(moduleTypeRegistry.getTriggers(locale, tags))));
}
} }
if (type == null || "condition".equals(type)) { if (type == null || "condition".equals(type)) {
modules.addAll(ConditionTypeDTOMapper.map(moduleTypeRegistry.getConditions(locale, tags))); if (modules != null) {
modules.addAll(ConditionTypeDTOMapper.map(moduleTypeRegistry.getConditions(locale, tags)));
} else if (modulesMap != null) {
modulesMap.put("conditions", new ArrayList<ModuleTypeDTO>(
ConditionTypeDTOMapper.map(moduleTypeRegistry.getConditions(locale, tags))));
}
} }
if (type == null || "action".equals(type)) { if (type == null || "action".equals(type)) {
modules.addAll(ActionTypeDTOMapper.map(moduleTypeRegistry.getActions(locale, tags))); if (modules != null) {
modules.addAll(ActionTypeDTOMapper.map(moduleTypeRegistry.getActions(locale, tags)));
} else if (modulesMap != null) {
modulesMap.put("actions", new ArrayList<ModuleTypeDTO>(
ActionTypeDTOMapper.map(moduleTypeRegistry.getActions(locale, tags))));
}
} }
return Response.ok(modules).build(); return Response.ok(modules != null ? modules : modulesMap).build();
} }
@GET @GET

View File

@ -212,7 +212,8 @@ public class RuleResource implements RESTResource {
Stream<EnrichedRuleDTO> rules = ruleRegistry.stream().filter(p) // filter according to Predicates Stream<EnrichedRuleDTO> rules = ruleRegistry.stream().filter(p) // filter according to Predicates
.map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); // map matching rules .map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); // map matching rules
if (summary != null && summary) { if (summary != null && summary) {
rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,status,tags,editable"); rules = dtoMapper.limitToFields(rules,
"uid,templateUID,templateState,name,visibility,description,status,tags,editable");
} }
return Response.ok(new Stream2JSONInputStream(rules)).build(); return Response.ok(new Stream2JSONInputStream(rules)).build();
@ -344,12 +345,12 @@ public class RuleResource implements RESTResource {
@Consumes(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN)
@Operation(operationId = "enableRule", summary = "Sets the rule enabled status.", responses = { @Operation(operationId = "enableRule", summary = "Sets the rule enabled status.", responses = {
@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID was not found.") })
public Response enableRule(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, public Response enableRule(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID,
@Parameter(description = "enable", required = true) String enabled) throws IOException { @Parameter(description = "enable", required = true) String enabled) throws IOException {
Rule rule = ruleRegistry.get(ruleUID); Rule rule = ruleRegistry.get(ruleUID);
if (rule == null) { if (rule == null) {
logger.info("Received HTTP PUT request for set enabled at '{}' for the unknown rule '{}'.", logger.info("Received HTTP POST request for set enabled at '{}' for the unknown rule '{}'.",
uriInfo.getPath(), ruleUID); uriInfo.getPath(), ruleUID);
return Response.status(Status.NOT_FOUND).build(); return Response.status(Status.NOT_FOUND).build();
} else { } else {
@ -358,13 +359,32 @@ public class RuleResource implements RESTResource {
} }
} }
@POST
@Path("/{ruleUID}/regenerate")
@Consumes(MediaType.TEXT_PLAIN)
@Operation(operationId = "regenerateRule", summary = "Regenerates the rule from its template.", responses = {
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "A template-based rule with the given UID was not found.") })
public Response regenerateRule(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID)
throws IOException {
try {
ruleRegistry.regenerateFromTemplate(ruleUID);
return Response.ok(null, MediaType.TEXT_PLAIN).build();
} catch (IllegalArgumentException e) {
logger.info(
"Received HTTP POST request for regenerating rule from template at '{}' for an invalid rule UID '{}'.",
uriInfo.getPath(), ruleUID);
return Response.status(Status.NOT_FOUND).build();
}
}
@POST @POST
@RolesAllowed({ Role.USER, Role.ADMIN }) @RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{ruleUID}/runnow") @Path("/{ruleUID}/runnow")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Operation(operationId = "runRuleNow", summary = "Executes actions of the rule.", responses = { @Operation(operationId = "runRuleNow", summary = "Executes actions of the rule.", responses = {
@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID was not found.") })
public Response runNow(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, public Response runNow(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID,
@Nullable @Parameter(description = "the context for running this rule", allowEmptyValue = true) Map<String, Object> context) @Nullable @Parameter(description = "the context for running this rule", allowEmptyValue = true) Map<String, Object> context)
throws IOException { throws IOException {

View File

@ -13,6 +13,7 @@
package org.openhab.core.automation; package org.openhab.core.automation;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -40,6 +41,7 @@ import org.openhab.core.config.core.Configuration;
* They can help the user to classify or label the Rules, and to filter and search them. * They can help the user to classify or label the Rules, and to filter and search them.
* *
* @author Kai Kreuzer - Initial contribution * @author Kai Kreuzer - Initial contribution
* @author Ravi Nadahar - Added TemplateState
*/ */
@NonNullByDefault @NonNullByDefault
public interface Rule extends Identifiable<String> { public interface Rule extends Identifiable<String> {
@ -66,6 +68,17 @@ public interface Rule extends Identifiable<String> {
@Nullable @Nullable
String getTemplateUID(); String getTemplateUID();
/**
* This method is used to track the template processing state by the {@link RuleRegistry}. The default
* implementation doesn't support templates and must be overridden if the {@link Rule} implementation
* supports templates.
*
* @return the current template processing state.
*/
default TemplateState getTemplateState() {
return TemplateState.NO_TEMPLATE;
}
/** /**
* This method is used to obtain the {@link Rule}'s human-readable name. * This method is used to obtain the {@link Rule}'s human-readable name.
* *
@ -154,4 +167,61 @@ public interface Rule extends Identifiable<String> {
} }
return null; return null;
} }
/**
* This enum represent the different states a rule can have in respect to rule templates.
*/
public enum TemplateState {
/** This {@link Rule} isn't associated with a template */
NO_TEMPLATE,
/** This {@link Rule} is associated with a template and it has yet to be instantiated */
PENDING,
/** This {@link Rule} is associated with a template that wasn't found */
TEMPLATE_MISSING,
/** This {@link Rule} is associated with a template and has been instantiated */
INSTANTIATED;
@Override
public String toString() {
switch (this) {
case INSTANTIATED:
return "instantiated";
case PENDING:
return "pending";
case TEMPLATE_MISSING:
return "template-missing";
case NO_TEMPLATE:
default:
return "no-template";
}
}
/**
* Returns the {@link TemplateState} that best represents the specified string. If no match is found,
* {@link TemplateState#NO_TEMPLATE} is returned.
*
* @param templateState the string to convert.
* @return The resulting {@link TemplateState}.
*/
public static TemplateState typeOf(@Nullable String templateState) {
if (templateState == null) {
return NO_TEMPLATE;
}
String s = templateState.trim().toLowerCase(Locale.ROOT);
switch (s) {
case "instantiated":
return INSTANTIATED;
case "pending":
return PENDING;
case "template-missing":
return TEMPLATE_MISSING;
default:
return NO_TEMPLATE;
}
}
}
} }

View File

@ -85,4 +85,13 @@ public interface RuleRegistry extends Registry<Rule, String> {
* @return collection of {@link Rule}s having specified tags. * @return collection of {@link Rule}s having specified tags.
*/ */
Collection<Rule> getByTags(String... tags); Collection<Rule> getByTags(String... tags);
/**
* This method triggers a new generation of the rule from its template by reverting the rule to its
* "rule stub" state only containing the template configuration.
*
* @param ruleUID the UID of the {@link Rule}.
* @throws IllegalArgumentException if the rule doesn't exist or isn't linked to a template.
*/
void regenerateFromTemplate(String ruleUID);
} }

View File

@ -64,6 +64,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* <td><b>N/A</b></td> * <td><b>N/A</b></td>
* </tr> * </tr>
* <tr> * <tr>
* <td><b>{@link #TEMPLATE_PENDING}</b></td>
* <td>Template processing pending</td>
* <td>Template processing pending</td>
* <td>Template processing pending</td>
* <td><b>N/A</b></td>
* </tr>
* <tr>
* <td><b>{@link #INVALID_RULE}</b></td> * <td><b>{@link #INVALID_RULE}</b></td>
* <td>Resolving failed</td> * <td>Resolving failed</td>
* <td><b>N/A</b></td> * <td><b>N/A</b></td>
@ -82,6 +89,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Yordan Mihaylov - Initial contribution * @author Yordan Mihaylov - Initial contribution
* @author Kai Kreuzer - Refactored to match ThingStatusDetail implementation * @author Kai Kreuzer - Refactored to match ThingStatusDetail implementation
* @author Ana Dimova - add java doc * @author Ana Dimova - add java doc
* @author Ravi Nadahar - added {@link #TEMPLATE_PENDING}
*/ */
@NonNullByDefault @NonNullByDefault
public enum RuleStatusDetail { public enum RuleStatusDetail {
@ -90,8 +98,9 @@ public enum RuleStatusDetail {
HANDLER_INITIALIZING_ERROR(2), HANDLER_INITIALIZING_ERROR(2),
CONFIGURATION_ERROR(3), CONFIGURATION_ERROR(3),
TEMPLATE_MISSING_ERROR(4), TEMPLATE_MISSING_ERROR(4),
INVALID_RULE(5), TEMPLATE_PENDING(5),
DISABLED(6); INVALID_RULE(6),
DISABLED(7);
private final int value; private final int value;

View File

@ -16,6 +16,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.core.automation.Visibility; import org.openhab.core.automation.Visibility;
import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO; import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO;
@ -26,15 +27,16 @@ import org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO;
*/ */
public class RuleDTO { public class RuleDTO {
public List<TriggerDTO> triggers; public List<@NonNull TriggerDTO> triggers;
public List<ConditionDTO> conditions; public List<@NonNull ConditionDTO> conditions;
public List<ActionDTO> actions; public List<@NonNull ActionDTO> actions;
public Map<String, Object> configuration; public Map<@NonNull String, @NonNull Object> configuration;
public List<ConfigDescriptionParameterDTO> configDescriptions; public List<@NonNull ConfigDescriptionParameterDTO> configDescriptions;
public String templateUID; public String templateUID;
public String templateState;
public String uid; public String uid;
public String name; public String name;
public Set<String> tags; public Set<@NonNull String> tags;
public Visibility visibility; public Visibility visibility;
public String description; public String description;
} }

View File

@ -14,6 +14,7 @@ package org.openhab.core.automation.dto;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.automation.Rule; import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Rule.TemplateState;
import org.openhab.core.automation.util.RuleBuilder; import org.openhab.core.automation.util.RuleBuilder;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.core.dto.ConfigDescriptionDTOMapper; import org.openhab.core.config.core.dto.ConfigDescriptionDTOMapper;
@ -34,13 +35,19 @@ public class RuleDTOMapper {
} }
public static Rule map(final RuleDTO ruleDto) { public static Rule map(final RuleDTO ruleDto) {
return RuleBuilder.create(ruleDto.uid).withActions(ActionDTOMapper.mapDto(ruleDto.actions)) RuleBuilder builder = RuleBuilder.create(ruleDto.uid).withActions(ActionDTOMapper.mapDto(ruleDto.actions))
.withConditions(ConditionDTOMapper.mapDto(ruleDto.conditions)) .withConditions(ConditionDTOMapper.mapDto(ruleDto.conditions))
.withTriggers(TriggerDTOMapper.mapDto(ruleDto.triggers)) .withTriggers(TriggerDTOMapper.mapDto(ruleDto.triggers))
.withConfiguration(new Configuration(ruleDto.configuration)) .withConfiguration(new Configuration(ruleDto.configuration))
.withConfigurationDescriptions(ConfigDescriptionDTOMapper.map(ruleDto.configDescriptions)) .withConfigurationDescriptions(ConfigDescriptionDTOMapper.map(ruleDto.configDescriptions))
.withTemplateUID(ruleDto.templateUID).withVisibility(ruleDto.visibility).withTags(ruleDto.tags) .withTemplateUID(ruleDto.templateUID).withVisibility(ruleDto.visibility).withTags(ruleDto.tags)
.withName(ruleDto.name).withDescription(ruleDto.description).build(); .withName(ruleDto.name).withDescription(ruleDto.description);
if (ruleDto.templateState == null) {
builder.withTemplateState(ruleDto.templateUID == null ? TemplateState.NO_TEMPLATE : TemplateState.PENDING);
} else {
builder.withTemplateState(TemplateState.typeOf(ruleDto.templateState));
}
return builder.build();
} }
protected static void fillProperties(final Rule from, final RuleDTO to) { protected static void fillProperties(final Rule from, final RuleDTO to) {
@ -50,6 +57,7 @@ public class RuleDTOMapper {
to.configuration = from.getConfiguration().getProperties(); to.configuration = from.getConfiguration().getProperties();
to.configDescriptions = ConfigDescriptionDTOMapper.mapParameters(from.getConfigurationDescriptions()); to.configDescriptions = ConfigDescriptionDTOMapper.mapParameters(from.getConfigurationDescriptions());
to.templateUID = from.getTemplateUID(); to.templateUID = from.getTemplateUID();
to.templateState = from.getTemplateState().toString();
to.uid = from.getUID(); to.uid = from.getUID();
to.name = from.getName(); to.name = from.getName();
to.tags = from.getTags(); to.tags = from.getTags();

View File

@ -39,6 +39,7 @@ import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Module; import org.openhab.core.automation.Module;
import org.openhab.core.automation.ModuleHandlerCallback; import org.openhab.core.automation.ModuleHandlerCallback;
import org.openhab.core.automation.Rule; import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Rule.TemplateState;
import org.openhab.core.automation.RuleExecution; import org.openhab.core.automation.RuleExecution;
import org.openhab.core.automation.RuleManager; import org.openhab.core.automation.RuleManager;
import org.openhab.core.automation.RuleRegistry; import org.openhab.core.automation.RuleRegistry;
@ -838,8 +839,17 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
return false; return false;
} }
// Set the module handlers and so check if all handlers are available.
final String ruleUID = rule.getUID(); final String ruleUID = rule.getUID();
TemplateState templateState = rule.unwrap().getTemplateState();
if (templateState == TemplateState.TEMPLATE_MISSING || templateState == TemplateState.PENDING) {
setStatus(ruleUID,
new RuleStatusInfo(RuleStatus.UNINITIALIZED,
templateState == TemplateState.TEMPLATE_MISSING ? RuleStatusDetail.TEMPLATE_MISSING_ERROR
: RuleStatusDetail.TEMPLATE_PENDING));
return false;
}
// Set the module handlers and so check if all handlers are available.
final String errMsgs = setModuleHandlers(ruleUID, rule.getModules()); final String errMsgs = setModuleHandlers(ruleUID, rule.getModules());
if (errMsgs != null) { if (errMsgs != null) {
setStatus(ruleUID, setStatus(ruleUID,
@ -1562,7 +1572,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
*/ */
private void compileRules() { private void compileRules() {
getScheduledExecutor().submit(() -> { getScheduledExecutor().submit(() -> {
ruleRegistry.getAll().stream() // ruleRegistry.stream() //
.filter(r -> isEnabled(r.getUID())) // .filter(r -> isEnabled(r.getUID())) //
.forEach(r -> compileRule(r.getUID())); .forEach(r -> compileRule(r.getUID()));
executeRulesWithStartLevel(); executeRulesWithStartLevel();
@ -1571,7 +1581,7 @@ public class RuleEngineImpl implements RuleManager, RegistryChangeListener<Modul
private void executeRulesWithStartLevel() { private void executeRulesWithStartLevel() {
getScheduledExecutor().submit(() -> { getScheduledExecutor().submit(() -> {
ruleRegistry.getAll().stream() // ruleRegistry.stream() //
.filter(this::mustTrigger) // .filter(this::mustTrigger) //
.forEach(r -> runNow(r.getUID(), true, .forEach(r -> runNow(r.getUID(), true,
Map.of(SystemTriggerHandler.OUT_STARTLEVEL, StartLevelService.STARTLEVEL_RULES, "event", Map.of(SystemTriggerHandler.OUT_STARTLEVEL, StartLevelService.STARTLEVEL_RULES, "event",

View File

@ -15,6 +15,7 @@ package org.openhab.core.automation.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -48,6 +49,7 @@ public class RuleImpl implements Rule {
protected Configuration configuration; protected Configuration configuration;
protected List<ConfigDescriptionParameter> configDescriptions; protected List<ConfigDescriptionParameter> configDescriptions;
protected @Nullable String templateUID; protected @Nullable String templateUID;
protected TemplateState templateStatus;
protected String uid; protected String uid;
protected @Nullable String name; protected @Nullable String name;
protected Set<String> tags; protected Set<String> tags;
@ -62,7 +64,7 @@ public class RuleImpl implements Rule {
* @param uid the rule's identifier, or {@code null} if a random identifier should be generated. * @param uid the rule's identifier, or {@code null} if a random identifier should be generated.
*/ */
public RuleImpl(@Nullable String uid) { public RuleImpl(@Nullable String uid) {
this(uid, null, null, null, null, null, null, null, null, null, null); this(uid, null, null, null, null, null, null, null, null, null, TemplateState.NO_TEMPLATE, null);
} }
/** /**
@ -90,7 +92,8 @@ public class RuleImpl implements Rule {
public RuleImpl(@Nullable String uid, final @Nullable String name, final @Nullable String description, public RuleImpl(@Nullable String uid, final @Nullable String name, final @Nullable String description,
final @Nullable Set<String> tags, @Nullable List<Trigger> triggers, @Nullable List<Condition> conditions, final @Nullable Set<String> tags, @Nullable List<Trigger> triggers, @Nullable List<Condition> conditions,
@Nullable List<Action> actions, @Nullable List<ConfigDescriptionParameter> configDescriptions, @Nullable List<Action> actions, @Nullable List<ConfigDescriptionParameter> configDescriptions,
@Nullable Configuration configuration, @Nullable String templateUID, @Nullable Visibility visibility) { @Nullable Configuration configuration, @Nullable String templateUID, TemplateState templateStatus,
@Nullable Visibility visibility) {
this.uid = uid == null ? UUID.randomUUID().toString() : uid; this.uid = uid == null ? UUID.randomUUID().toString() : uid;
this.name = name; this.name = name;
this.description = description; this.description = description;
@ -102,6 +105,7 @@ public class RuleImpl implements Rule {
this.configuration = configuration == null ? new Configuration() this.configuration = configuration == null ? new Configuration()
: new Configuration(configuration.getProperties()); : new Configuration(configuration.getProperties());
this.templateUID = templateUID; this.templateUID = templateUID;
this.templateStatus = templateStatus;
this.visibility = visibility == null ? Visibility.VISIBLE : visibility; this.visibility = visibility == null ? Visibility.VISIBLE : visibility;
} }
@ -124,6 +128,20 @@ public class RuleImpl implements Rule {
this.templateUID = templateUID; this.templateUID = templateUID;
} }
@Override
public TemplateState getTemplateState() {
return templateStatus;
}
/**
* This method is used to specify the current rule template state.
*
* @param templateState the {@link TemplateState} to set.
*/
public void setTemplateStatus(TemplateState templateState) {
this.templateStatus = Objects.requireNonNull(templateState);
}
@Override @Override
public @Nullable String getName() { public @Nullable String getName() {
return name; return name;

View File

@ -20,18 +20,21 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.ManagedRuleProvider; import org.openhab.core.automation.ManagedRuleProvider;
import org.openhab.core.automation.Module; import org.openhab.core.automation.Module;
import org.openhab.core.automation.Rule; import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Rule.TemplateState;
import org.openhab.core.automation.RuleProvider; import org.openhab.core.automation.RuleProvider;
import org.openhab.core.automation.RuleRegistry; import org.openhab.core.automation.RuleRegistry;
import org.openhab.core.automation.RuleStatus; import org.openhab.core.automation.RuleStatus;
import org.openhab.core.automation.RuleStatusInfo; import org.openhab.core.automation.RuleStatusInfo;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.internal.template.RuleTemplateRegistry; import org.openhab.core.automation.internal.template.RuleTemplateRegistry;
import org.openhab.core.automation.template.RuleTemplate; import org.openhab.core.automation.template.RuleTemplate;
import org.openhab.core.automation.template.TemplateRegistry; import org.openhab.core.automation.template.TemplateRegistry;
@ -101,6 +104,7 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - refactored (managed) provider and registry implementation and other fixes * @author Kai Kreuzer - refactored (managed) provider and registry implementation and other fixes
* @author Benedikt Niehues - added events for rules * @author Benedikt Niehues - added events for rules
* @author Victor Toni - return only copies of {@link Rule}s * @author Victor Toni - return only copies of {@link Rule}s
* @author Ravi Nadahar - added support for regenerating {@link Rule}s from {@link RuleTemplate}s.
*/ */
@NonNullByDefault @NonNullByDefault
@Component(service = RuleRegistry.class, immediate = true) @Component(service = RuleRegistry.class, immediate = true)
@ -108,17 +112,14 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
implements RuleRegistry, RegistryChangeListener<RuleTemplate> { implements RuleRegistry, RegistryChangeListener<RuleTemplate> {
private static final String SOURCE = RuleRegistryImpl.class.getSimpleName(); private static final String SOURCE = RuleRegistryImpl.class.getSimpleName();
private static final Set<TemplateState> PROCESSABLE_TEMPLATE_STATES = Set.of(TemplateState.PENDING,
TemplateState.TEMPLATE_MISSING);
private final Logger logger = LoggerFactory.getLogger(RuleRegistryImpl.class.getName()); private final Logger logger = LoggerFactory.getLogger(RuleRegistryImpl.class.getName());
private @NonNullByDefault({}) ModuleTypeRegistry moduleTypeRegistry; private @NonNullByDefault({}) ModuleTypeRegistry moduleTypeRegistry;
private @NonNullByDefault({}) RuleTemplateRegistry templateRegistry; private @NonNullByDefault({}) RuleTemplateRegistry templateRegistry;
/**
* {@link Map} of template UIDs to rules where these templates participated.
*/
private final Map<String, Set<String>> mapTemplateToRules = new HashMap<>();
/** /**
* Constructor that is responsible to invoke the super constructor with appropriate providerClazz * Constructor that is responsible to invoke the super constructor with appropriate providerClazz
* {@link RuleProvider} - the class of the providers that should be tracked automatically after activation. * {@link RuleProvider} - the class of the providers that should be tracked automatically after activation.
@ -290,15 +291,6 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
postEvent(RuleEventFactory.createRuleStatusInfoEvent(statusInfo, ruleUID, SOURCE)); postEvent(RuleEventFactory.createRuleStatusInfoEvent(statusInfo, ruleUID, SOURCE));
} }
@Override
protected void onRemoveElement(Rule rule) {
String uid = rule.getUID();
String templateUID = rule.getTemplateUID();
if (templateUID != null) {
updateRuleTemplateMapping(templateUID, uid, true);
}
}
@Override @Override
protected void notifyListenersAboutRemovedElement(Rule element) { protected void notifyListenersAboutRemovedElement(Rule element) {
super.notifyListenersAboutRemovedElement(element); super.notifyListenersAboutRemovedElement(element);
@ -336,6 +328,44 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
return result; return result;
} }
@Override
public void regenerateFromTemplate(String ruleUID) {
Rule rule = get(ruleUID);
if (rule == null) {
throw new IllegalArgumentException(
"Can't regenerate rule from template because no rule with UID \"" + ruleUID + "\" exists");
}
if (rule.getTemplateUID() == null || rule.getTemplateState() == TemplateState.NO_TEMPLATE) {
throw new IllegalArgumentException(
"Can't regenerate rule from template because the rule isn't linked to a template");
}
try {
Rule resolvedRule = resolveRuleByTemplate(
RuleBuilder.create(rule).withActions((List<Action>) null).withConditions((List<Condition>) null)
.withTriggers((List<Trigger>) null).withTemplateState(TemplateState.PENDING).build());
Provider<Rule> provider = getProvider(rule.getUID());
if (provider == null) {
logger.error("Regenerating rule '{}' from template failed because the provider is unknown",
rule.getUID());
return;
}
if (provider instanceof ManagedRuleProvider) {
update(resolvedRule);
} else {
updated(provider, rule, resolvedRule);
}
if (resolvedRule.getTemplateState() == TemplateState.TEMPLATE_MISSING) {
logger.warn("Failed to regenerate rule '{}' from template since the template is missing",
rule.getUID());
} else {
logger.info("Rule '{}' was regenerated from template '{}'", rule.getUID(), rule.getTemplateUID());
}
} catch (IllegalArgumentException e) {
logger.error("Regenerating rule '{}' from template failed: {}", rule.getUID(), e.getMessage(), e);
throw e;
}
}
/** /**
* The method checks if the rule has to be resolved by template or not. If the rule does not contain tempateUID it * The method checks if the rule has to be resolved by template or not. If the rule does not contain tempateUID it
* returns same rule, otherwise it tries to resolve the rule created from template. If the template is available * returns same rule, otherwise it tries to resolve the rule created from template. If the template is available
@ -347,46 +377,30 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
* missing. * missing.
*/ */
private Rule resolveRuleByTemplate(Rule rule) { private Rule resolveRuleByTemplate(Rule rule) {
TemplateState templateState = rule.getTemplateState();
if (templateState == TemplateState.NO_TEMPLATE || templateState == TemplateState.INSTANTIATED) {
return rule;
}
String templateUID = rule.getTemplateUID(); String templateUID = rule.getTemplateUID();
if (templateUID == null) { if (templateUID == null) {
return rule; return rule;
} }
RuleTemplate template = templateRegistry.get(templateUID); RuleTemplate template = templateRegistry.get(templateUID);
String uid = rule.getUID();
if (template == null) { if (template == null) {
updateRuleTemplateMapping(templateUID, uid, false); if (templateState == TemplateState.TEMPLATE_MISSING) {
return rule;
}
logger.debug("Rule template {} does not exist.", templateUID); logger.debug("Rule template {} does not exist.", templateUID);
return rule; return RuleBuilder.create(rule).withTemplateState(TemplateState.TEMPLATE_MISSING).build();
} else { } else {
RuleImpl resolvedRule = (RuleImpl) RuleBuilder RuleImpl resolvedRule = (RuleImpl) RuleBuilder
.create(template, rule.getUID(), rule.getName(), rule.getConfiguration(), rule.getVisibility()) .create(template, rule.getUID(), rule.getName(), rule.getConfiguration(), rule.getVisibility())
.build(); .build();
resolveConfigurations(resolvedRule); resolveConfigurations(resolvedRule);
updateRuleTemplateMapping(templateUID, uid, true);
return resolvedRule; return resolvedRule;
} }
} }
/**
* Updates the content of the {@link Map} that maps the template to rules, using it to complete their definitions.
*
* @param templateUID the {@link RuleTemplate}'s UID specifying the template.
* @param ruleUID the {@link Rule}'s UID specifying a rule created by the specified template.
* @param resolved specifies if the {@link Map} should be updated by adding or removing the specified rule
* accordingly if the rule is resolved or not.
*/
private void updateRuleTemplateMapping(String templateUID, String ruleUID, boolean resolved) {
synchronized (this) {
Set<String> ruleUIDs = Objects
.requireNonNull(mapTemplateToRules.computeIfAbsent(templateUID, k -> new HashSet<>()));
if (resolved) {
ruleUIDs.remove(ruleUID);
} else {
ruleUIDs.add(ruleUID);
}
}
}
@Override @Override
protected void addProvider(Provider<Rule> provider) { protected void addProvider(Provider<Rule> provider) {
super.addProvider(provider); super.addProvider(provider);
@ -474,7 +488,8 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
ConfigurationNormalizer.normalizeConfiguration(configuration, ConfigurationNormalizer.normalizeConfiguration(configuration,
ConfigurationNormalizer.getConfigDescriptionMap(configDescriptions)); ConfigurationNormalizer.getConfigDescriptionMap(configDescriptions));
Map<String, Object> configurationProperties = configuration.getProperties(); Map<String, Object> configurationProperties = configuration.getProperties();
if (rule.getTemplateUID() == null) { TemplateState templateState = rule.getTemplateState();
if (templateState == TemplateState.INSTANTIATED || templateState == TemplateState.NO_TEMPLATE) {
String uid = rule.getUID(); String uid = rule.getUID();
try { try {
validateConfiguration(configDescriptions, new HashMap<>(configurationProperties)); validateConfiguration(configDescriptions, new HashMap<>(configurationProperties));
@ -622,38 +637,7 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
@Override @Override
public void added(RuleTemplate element) { public void added(RuleTemplate element) {
String templateUID = element.getUID(); processRuleStubs(element);
Set<String> rules = new HashSet<>();
synchronized (this) {
Set<String> rulesForResolving = mapTemplateToRules.get(templateUID);
if (rulesForResolving != null) {
rules.addAll(rulesForResolving);
}
}
for (String rUID : rules) {
try {
Rule unresolvedRule = get(rUID);
if (unresolvedRule != null) {
Rule resolvedRule = resolveRuleByTemplate(unresolvedRule);
Provider<Rule> provider = getProvider(rUID);
if (provider instanceof ManagedRuleProvider) {
update(resolvedRule);
} else if (provider != null) {
updated(provider, unresolvedRule, unresolvedRule);
} else {
logger.error(
"Resolving the rule '{}' by template '{}' failed because the provider is not known",
rUID, templateUID);
}
} else {
logger.error(
"Resolving the rule '{}' by template '{}' failed because it is not known to the registry",
rUID, templateUID);
}
} catch (IllegalArgumentException e) {
logger.error("Resolving the rule '{}' by template '{}' failed", rUID, templateUID, e);
}
}
} }
@Override @Override
@ -663,6 +647,34 @@ public class RuleRegistryImpl extends AbstractRegistry<Rule, String, RuleProvide
@Override @Override
public void updated(RuleTemplate oldElement, RuleTemplate element) { public void updated(RuleTemplate oldElement, RuleTemplate element) {
// Do nothing - resolved rules are independent from templates processRuleStubs(element);
}
/**
* Processes any existing rule stubs (rules with a template specified that haven't yet been converted into "proper
* rules") that references the specified rule template using the new or updated rule template.
*
* @param template the {@link RuleTemplate} to use for processing matching rule stubs.
*/
protected void processRuleStubs(RuleTemplate template) {
String templateUID = template.getUID();
List<Rule> rules = stream().filter((r) -> PROCESSABLE_TEMPLATE_STATES.contains(r.getTemplateState())).toList();
for (Rule unresolvedRule : rules) {
try {
Rule resolvedRule = resolveRuleByTemplate(unresolvedRule);
Provider<Rule> provider = getProvider(unresolvedRule.getUID());
if (provider instanceof ManagedRuleProvider) {
update(resolvedRule);
} else if (provider != null) {
updated(provider, unresolvedRule, resolvedRule);
} else {
logger.error("Resolving the rule '{}' by template '{}' failed because the provider is not known",
unresolvedRule.getUID(), templateUID);
}
} catch (IllegalArgumentException e) {
logger.error("Resolving the rule '{}' by template '{}' failed", unresolvedRule.getUID(), templateUID,
e);
}
}
} }
} }

View File

@ -24,6 +24,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Action; import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition; import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Rule; import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Rule.TemplateState;
import org.openhab.core.automation.Trigger; import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.Visibility; import org.openhab.core.automation.Visibility;
import org.openhab.core.automation.internal.RuleImpl; import org.openhab.core.automation.internal.RuleImpl;
@ -45,6 +46,7 @@ public class RuleBuilder {
private Configuration configuration; private Configuration configuration;
private List<ConfigDescriptionParameter> configDescriptions; private List<ConfigDescriptionParameter> configDescriptions;
private @Nullable String templateUID; private @Nullable String templateUID;
private TemplateState templateState;
private final String uid; private final String uid;
private @Nullable String name; private @Nullable String name;
private Set<String> tags; private Set<String> tags;
@ -58,6 +60,7 @@ public class RuleBuilder {
this.configuration = new Configuration(rule.getConfiguration()); this.configuration = new Configuration(rule.getConfiguration());
this.configDescriptions = new LinkedList<>(rule.getConfigurationDescriptions()); this.configDescriptions = new LinkedList<>(rule.getConfigurationDescriptions());
this.templateUID = rule.getTemplateUID(); this.templateUID = rule.getTemplateUID();
this.templateState = TemplateState.NO_TEMPLATE;
this.uid = rule.getUID(); this.uid = rule.getUID();
this.name = rule.getName(); this.name = rule.getName();
this.tags = new HashSet<>(rule.getTags()); this.tags = new HashSet<>(rule.getTags());
@ -74,7 +77,8 @@ public class RuleBuilder {
return create(r.getUID()).withActions(r.getActions()).withConditions(r.getConditions()) return create(r.getUID()).withActions(r.getActions()).withConditions(r.getConditions())
.withTriggers(r.getTriggers()).withConfiguration(r.getConfiguration()) .withTriggers(r.getTriggers()).withConfiguration(r.getConfiguration())
.withConfigurationDescriptions(r.getConfigurationDescriptions()).withDescription(r.getDescription()) .withConfigurationDescriptions(r.getConfigurationDescriptions()).withDescription(r.getDescription())
.withName(r.getName()).withTags(r.getTags()); .withName(r.getName()).withTags(r.getTags()).withTemplateUID(r.getTemplateUID())
.withTemplateState(r.getTemplateState());
} }
public static RuleBuilder create(RuleTemplate template, String uid, @Nullable String name, public static RuleBuilder create(RuleTemplate template, String uid, @Nullable String name,
@ -82,7 +86,8 @@ public class RuleBuilder {
return create(uid).withActions(template.getActions()).withConditions(template.getConditions()) return create(uid).withActions(template.getActions()).withConditions(template.getConditions())
.withTriggers(template.getTriggers()).withConfiguration(configuration) .withTriggers(template.getTriggers()).withConfiguration(configuration)
.withConfigurationDescriptions(template.getConfigurationDescriptions()) .withConfigurationDescriptions(template.getConfigurationDescriptions())
.withDescription(template.getDescription()).withName(name).withTags(template.getTags()); .withDescription(template.getDescription()).withName(name).withTags(template.getTags())
.withTemplateState(TemplateState.INSTANTIATED).withTemplateUID(template.getUID());
} }
public RuleBuilder withName(@Nullable String name) { public RuleBuilder withName(@Nullable String name) {
@ -100,6 +105,11 @@ public class RuleBuilder {
return this; return this;
} }
public RuleBuilder withTemplateState(TemplateState templateState) {
this.templateState = templateState;
return this;
}
public RuleBuilder withVisibility(@Nullable Visibility visibility) { public RuleBuilder withVisibility(@Nullable Visibility visibility) {
this.visibility = visibility; this.visibility = visibility;
return this; return this;
@ -166,6 +176,6 @@ public class RuleBuilder {
public Rule build() { public Rule build() {
return new RuleImpl(uid, name, description, tags, triggers, conditions, actions, configDescriptions, return new RuleImpl(uid, name, description, tags, triggers, conditions, actions, configDescriptions,
configuration, templateUID, visibility); configuration, templateUID, templateState, visibility);
} }
} }

View File

@ -38,6 +38,7 @@ import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition; import org.openhab.core.automation.Condition;
import org.openhab.core.automation.ManagedRuleProvider; import org.openhab.core.automation.ManagedRuleProvider;
import org.openhab.core.automation.Rule; import org.openhab.core.automation.Rule;
import org.openhab.core.automation.Rule.TemplateState;
import org.openhab.core.automation.RuleManager; import org.openhab.core.automation.RuleManager;
import org.openhab.core.automation.RuleProvider; import org.openhab.core.automation.RuleProvider;
import org.openhab.core.automation.RuleRegistry; import org.openhab.core.automation.RuleRegistry;
@ -678,7 +679,7 @@ public class AutomationIntegrationTest extends JavaOSGiTest {
configs.put("updateItem", "templ_LampItem"); configs.put("updateItem", "templ_LampItem");
configs.put("updateCommand", "ON"); configs.put("updateCommand", "ON");
Rule templateRule = RuleBuilder.create("templateRuleUID").withTemplateUID("SimpleTestTemplate") Rule templateRule = RuleBuilder.create("templateRuleUID").withTemplateUID("SimpleTestTemplate")
.withConfiguration(new Configuration(configs)).build(); .withTemplateState(TemplateState.PENDING).withConfiguration(new Configuration(configs)).build();
ruleRegistry.add(templateRule); ruleRegistry.add(templateRule);
assertThat(ruleRegistry.get(templateRule.getUID()), is(notNullValue())); assertThat(ruleRegistry.get(templateRule.getUID()), is(notNullValue()));
@ -727,7 +728,7 @@ public class AutomationIntegrationTest extends JavaOSGiTest {
configs.put("updateCommand", "ON"); configs.put("updateCommand", "ON");
Configuration config = new Configuration(configs); Configuration config = new Configuration(configs);
Rule templateRule = RuleBuilder.create("xtemplateRuleUID").withTemplateUID("TestTemplateWithCompositeModules") Rule templateRule = RuleBuilder.create("xtemplateRuleUID").withTemplateUID("TestTemplateWithCompositeModules")
.withConfiguration(config).build(); .withTemplateState(TemplateState.PENDING).withConfiguration(config).build();
ruleRegistry.add(templateRule); ruleRegistry.add(templateRule);
assertThat(ruleRegistry.get(templateRule.getUID()), is(notNullValue())); assertThat(ruleRegistry.get(templateRule.getUID()), is(notNullValue()));