Regenerate rules from templates (#4718)
* Rule template regeneration support Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>pull/4829/head
parent
fb62bf33cd
commit
5b12280f5b
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
Loading…
Reference in New Issue