Skip to content

Regenerate rules from templates #4718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ public void removeAllAddedByScript() {
public Collection<Rule> getByTags(String... tags) {
return ruleRegistry.getByTags(tags);
}

@Override
public void regenerateFromTemplate(String ruleUID) {
ruleRegistry.regenerateFromTemplate(ruleUID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
"conditions": [
{
"uid": "jsr223.ScriptedCondition",
"label": "Scripted condition",
"description": "allows the definition of a condition by a script",
"label": "Opaque Condition",
"description": "Evaluates a condition using external code. See the rule source for details.",
"visibility": "EXPERT",
"configDescriptions": [
{
"name": "privId",
"type": "TEXT",
"description": "the identifier of the private method",
"description": "The identifier of the private method",
"required": true
}
]
Expand All @@ -18,23 +18,23 @@
"actions": [
{
"uid": "jsr223.ScriptedAction",
"label": "Scripted action",
"description": "allows the execution of a method defined by a script",
"label": "Opaque Action",
"description": "Executes external code. See the rule source for details.",
"visibility": "EXPERT",
"configDescriptions": [
{
"name": "privId",
"type": "TEXT",
"description": "the identifier of the private method",
"description": "The identifier of the private method",
"required": true
}
],
"outputs": [
{
"name": "result",
"type": "java.lang.Object",
"label": "result",
"description": "the script result.",
"label": "Result",
"description": "The script result",
"reference": ""
}
]
Expand All @@ -43,23 +43,23 @@
"triggers": [
{
"uid": "jsr223.ScriptedTrigger",
"label": "Scripted trigger",
"description": "allows the execution of a method defined by a script",
"label": "Opaque Trigger",
"description": "A trigger controlled by external code. See the rule source for details.",
"visibility": "EXPERT",
"configDescriptions": [
{
"name": "privId",
"type": "TEXT",
"description": "the identifier of the private method",
"description": "The identifier of the private method",
"required": true
}
],
"outputs": [
{
"name": "triggerOutput",
"type": "java.lang.String",
"label": "TriggerOutput label",
"description": "Text from the trigger",
"label": "Trigger Output",
"description": "The text from the trigger",
"reference": "consoleInput",
"defaultValue": "dtag"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# jsr223.ScriptedAction

module-type.jsr223.ScriptedAction.label = Scripted action
module-type.jsr223.ScriptedAction.description = allows the execution of a method defined by a script
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.description = the script result.
module-type.jsr223.ScriptedAction.label = Opaque Action
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.output.result.label = Result
module-type.jsr223.ScriptedAction.output.result.description = The script result

# jsr223.ScriptedCondition

module-type.jsr223.ScriptedCondition.label = Scripted condition
module-type.jsr223.ScriptedCondition.description = allows the definition of a condition by a script
module-type.jsr223.ScriptedCondition.config.privId.description = the identifier of the private method
module-type.jsr223.ScriptedCondition.label = Opaque Condition
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

# jsr223.ScriptedTrigger

module-type.jsr223.ScriptedTrigger.label = Scripted trigger
module-type.jsr223.ScriptedTrigger.description = allows the execution of a method defined by a script
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.description = Text from the trigger
module-type.jsr223.ScriptedTrigger.label = Opaque Trigger
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.output.triggerOutput.label = Trigger Output
module-type.jsr223.ScriptedTrigger.output.triggerOutput.description = The text from the trigger
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ private ModuleType getScriptActionType(@Nullable Locale locale) {
List<Output> outputs = new ArrayList<>();
Output result = new Output("result", "java.lang.Object", "result", "the script result", null, null, null);
outputs.add(result);
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);
return new ActionType(ScriptActionHandler.TYPE_ID, getConfigDescriptions(locale), "Execute an inline script",
"Executes a user-defined script", null, Visibility.VISIBLE, null, outputs);
}

private ModuleType getScriptConditionType(@Nullable Locale 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);
}

Expand All @@ -111,11 +111,11 @@ private List<ConfigDescriptionParameter> getConfigDescriptions(@Nullable Locale
}
final ConfigDescriptionParameter scriptType = ConfigDescriptionParameterBuilder.create("type", Type.TEXT)
.withRequired(true).withMultiple(false).withLabel("Script Type")
.withDescription("the scripting language used").withOptions(parameterOptionsList)
.withDescription("The scripting language used").withOptions(parameterOptionsList)
.withLimitToOptions(true).build();
final ConfigDescriptionParameter script = ConfigDescriptionParameterBuilder.create("script", Type.TEXT)
.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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
package org.openhab.core.automation.rest.internal;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
Expand Down Expand Up @@ -100,21 +102,43 @@ public ModuleTypeResource( //
public Response getAll(
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language,
@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 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)) {
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)) {
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)) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ public Response get(@Context SecurityContext securityContext, @Context Request r
Stream<EnrichedRuleDTO> rules = ruleRegistry.stream().filter(p) // filter according to Predicates
.map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); // map matching rules
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();
Expand Down Expand Up @@ -344,12 +345,12 @@ public Response updateConfiguration(@PathParam("ruleUID") @Parameter(description
@Consumes(MediaType.TEXT_PLAIN)
@Operation(operationId = "enableRule", summary = "Sets the rule enabled status.", responses = {
@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,
@Parameter(description = "enable", required = true) String enabled) throws IOException {
Rule rule = ruleRegistry.get(ruleUID);
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);
return Response.status(Status.NOT_FOUND).build();
} else {
Expand All @@ -358,13 +359,32 @@ public Response enableRule(@PathParam("ruleUID") @Parameter(description = "ruleU
}
}

@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 base rule with to 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
@RolesAllowed({ Role.USER, Role.ADMIN })
@Path("/{ruleUID}/runnow")
@Consumes(MediaType.APPLICATION_JSON)
@Operation(operationId = "runRuleNow", summary = "Executes actions of the rule.", responses = {
@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,
@Nullable @Parameter(description = "the context for running this rule", allowEmptyValue = true) Map<String, Object> context)
throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.core.automation;

import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -40,6 +41,7 @@
* They can help the user to classify or label the Rules, and to filter and search them.
*
* @author Kai Kreuzer - Initial contribution
* @author Ravi Nadahar - Added TemplateState
*/
@NonNullByDefault
public interface Rule extends Identifiable<String> {
Expand All @@ -66,6 +68,17 @@ public interface Rule extends Identifiable<String> {
@Nullable
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.
*
Expand Down Expand Up @@ -154,4 +167,61 @@ public interface Rule extends Identifiable<String> {
}
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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,13 @@ public interface RuleRegistry extends Registry<Rule, String> {
* @return collection of {@link Rule}s having specified 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);
}
Loading