-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Trigger condition resolver which parses and evaluates the Trigger…
… expression. Signed-off-by: Saurabh Singh <sisurab@amazon.com>
- Loading branch information
1 parent
5f4d9fd
commit c18a9a3
Showing
14 changed files
with
597 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...ting/src/main/kotlin/org/opensearch/alerting/triggercondition/parsers/ExpressionParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.alerting.triggercondition.parsers | ||
|
||
import org.opensearch.alerting.triggercondition.resolvers.TriggerExpressionResolver | ||
|
||
interface ExpressionParser { | ||
fun parse(): TriggerExpressionResolver | ||
} |
53 changes: 53 additions & 0 deletions
53
...c/main/kotlin/org/opensearch/alerting/triggercondition/parsers/TriggerExpressionParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.alerting.triggercondition.parsers | ||
|
||
import org.opensearch.alerting.triggercondition.resolvers.TriggerExpressionRPNResolver | ||
import org.opensearch.alerting.triggercondition.tokens.TriggerExpressionOperator | ||
|
||
/** | ||
* The postfix (Reverse Polish Notation) parser. | ||
* Uses the Shunting-yard algorithm to parse a mathematical expression | ||
* @param triggerExpression String containing the trigger expression for the monitor | ||
*/ | ||
class TriggerExpressionParser( | ||
triggerExpression: String | ||
) : TriggerExpressionRPNBaseParser(triggerExpression) { | ||
|
||
override fun parse(): TriggerExpressionRPNResolver { | ||
val expression = expressionToParse.replace(" ", "") | ||
|
||
val splitters = ArrayList<String>() | ||
TriggerExpressionOperator.values().forEach { splitters.add(it.value) } | ||
|
||
val breaks = ArrayList<String>().apply { add(expression) } | ||
for (s in splitters) { | ||
val a = ArrayList<String>() | ||
for (ind in 0 until breaks.size) { | ||
breaks[ind].let { | ||
if (it.length > 1) { | ||
a.addAll(breakString(breaks[ind], s)) | ||
} else a.add(it) | ||
} | ||
} | ||
breaks.clear() | ||
breaks.addAll(a) | ||
} | ||
|
||
return TriggerExpressionRPNResolver(convertInfixToPostfix(breaks)) | ||
} | ||
|
||
private fun breakString(input: String, delimeter: String): ArrayList<String> { | ||
val tokens = input.split(delimeter) | ||
val array = ArrayList<String>() | ||
for (t in tokens) { | ||
array.add(t) | ||
array.add(delimeter) | ||
} | ||
array.removeAt(array.size - 1) | ||
return array | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
...kotlin/org/opensearch/alerting/triggercondition/parsers/TriggerExpressionRPNBaseParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.alerting.triggercondition.parsers | ||
|
||
import org.opensearch.alerting.triggercondition.tokens.ExpressionToken | ||
import org.opensearch.alerting.triggercondition.tokens.TriggerExpressionConstant | ||
import org.opensearch.alerting.triggercondition.tokens.TriggerExpressionOperator | ||
import org.opensearch.alerting.triggercondition.tokens.TriggerExpressionToken | ||
import java.util.Stack | ||
|
||
/** | ||
* This is the abstract base class which holds the trigger expression parsing logic; | ||
* using the Infix to Postfix a.k.a. Reverse Polish Notation (RPN) parser. | ||
* It also uses the Shunting-Yard algorithm to parse the given trigger expression. | ||
* | ||
* @param expressionToParse Complete string containing the trigger expression | ||
*/ | ||
abstract class TriggerExpressionRPNBaseParser( | ||
protected val expressionToParse: String | ||
) : ExpressionParser { | ||
/** | ||
* To perform the Infix-to-postfix conversion of the trigger expression | ||
*/ | ||
protected fun convertInfixToPostfix(expTokens: List<String>): ArrayList<ExpressionToken> { | ||
val expTokenStack = Stack<ExpressionToken>() | ||
val outputExpTokens = ArrayList<ExpressionToken>() | ||
|
||
for (tokenString in expTokens) { | ||
if (tokenString.isEmpty()) continue | ||
when (val expToken = assignToken(tokenString)) { | ||
is TriggerExpressionToken -> outputExpTokens.add(expToken) | ||
is TriggerExpressionOperator -> { | ||
when (expToken) { | ||
TriggerExpressionOperator.PAR_LEFT -> expTokenStack.push(expToken) | ||
TriggerExpressionOperator.PAR_RIGHT -> { | ||
var topExpToken = expTokenStack.popExpTokenOrNull<TriggerExpressionOperator>() | ||
while (topExpToken != null && topExpToken != TriggerExpressionOperator.PAR_LEFT) { | ||
outputExpTokens.add(topExpToken) | ||
topExpToken = expTokenStack.popExpTokenOrNull<TriggerExpressionOperator>() | ||
} | ||
if (topExpToken != TriggerExpressionOperator.PAR_LEFT) | ||
throw java.lang.IllegalArgumentException("No matching left parenthesis.") | ||
} | ||
else -> { | ||
var op2 = expTokenStack.peekExpTokenOrNull<TriggerExpressionOperator>() | ||
while (op2 != null) { | ||
val c = expToken.precedence.compareTo(op2.precedence) | ||
if (c < 0 || !expToken.rightAssociative && c <= 0) { | ||
outputExpTokens.add(expTokenStack.pop()) | ||
} else { | ||
break | ||
} | ||
op2 = expTokenStack.peekExpTokenOrNull<TriggerExpressionOperator>() | ||
} | ||
expTokenStack.push(expToken) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
while (!expTokenStack.isEmpty()) { | ||
expTokenStack.peekExpTokenOrNull<TriggerExpressionOperator>()?.let { | ||
if (it == TriggerExpressionOperator.PAR_LEFT) | ||
throw java.lang.IllegalArgumentException("No matching right parenthesis.") | ||
} | ||
val top = expTokenStack.pop() | ||
outputExpTokens.add(top) | ||
} | ||
|
||
return outputExpTokens | ||
} | ||
|
||
/** | ||
* Looks up and maps the expression token that matches the string version of that expression unit | ||
*/ | ||
private fun assignToken(tokenString: String): ExpressionToken { | ||
|
||
// Check "query" string in trigger expression such as in 'query[name="abc"]' | ||
if (tokenString.startsWith(TriggerExpressionConstant.ConstantType.QUERY.ident)) | ||
return TriggerExpressionToken(tokenString) | ||
|
||
// Check operators in trigger expression such as in [&&, ||, !] | ||
for (op in TriggerExpressionOperator.values()) { | ||
if (op.value == tokenString) return op | ||
} | ||
|
||
// Check any constants in trigger expression such as in ["name, "id", "tag", [", "]", "="] | ||
for (con in TriggerExpressionConstant.ConstantType.values()) { | ||
if (tokenString == con.ident) return TriggerExpressionConstant(con) | ||
} | ||
|
||
throw IllegalArgumentException("Error while processing the trigger expression '$tokenString'") | ||
} | ||
|
||
private inline fun <reified T> Stack<ExpressionToken>.popExpTokenOrNull(): T? { | ||
return try { | ||
pop() as T | ||
} catch (e: java.lang.Exception) { | ||
null | ||
} | ||
} | ||
|
||
private inline fun <reified T> Stack<ExpressionToken>.peekExpTokenOrNull(): T? { | ||
return try { | ||
peek() as T | ||
} catch (e: java.lang.Exception) { | ||
null | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...g/src/main/kotlin/org/opensearch/alerting/triggercondition/resolvers/TriggerExpression.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.alerting.triggercondition.resolvers | ||
|
||
sealed class TriggerExpression { | ||
|
||
fun resolve(): Set<String> = when (this) { | ||
is And -> resolveAnd(docSet1, docSet2) | ||
is Or -> resolveOr(docSet1, docSet2) | ||
is Not -> resolveNot(allDocs, docSet2) | ||
} | ||
|
||
private fun resolveAnd(documentSet1: Set<String>, documentSet2: Set<String>): Set<String> { | ||
return documentSet1.intersect(documentSet2) | ||
} | ||
|
||
private fun resolveOr(documentSet1: Set<String>, documentSet2: Set<String>): Set<String> { | ||
return documentSet1.union(documentSet2) | ||
} | ||
|
||
private fun resolveNot(allDocs: Set<String>, documentSet2: Set<String>): Set<String> { | ||
return allDocs.subtract(documentSet2) | ||
} | ||
|
||
// Operators implemented as operator functions | ||
class And(val docSet1: Set<String>, val docSet2: Set<String>) : TriggerExpression() | ||
class Or(val docSet1: Set<String>, val docSet2: Set<String>) : TriggerExpression() | ||
class Not(val allDocs: Set<String>, val docSet2: Set<String>) : TriggerExpression() | ||
} |
Oops, something went wrong.