Skip to content

Commit 2f0b2ad

Browse files
Rohit0301Dev0907
authored andcommitted
feat(dsl): Add OpenMetadata DSL for alert rules and governance
- Add DSL engine for evaluating alert rules and governance policies - Support field access, comparisons (==, !=, >, <, >=, <=) - Support logical operators (AND, OR, NOT) - Built-in functions: contains, startsWith, length, matches, hasTag, hasChanged, isOwner - Fix ReDoS vulnerability by limiting regex complexity - Add comprehensive unit tests
1 parent 3c4f5e6 commit 2f0b2ad

15 files changed

Lines changed: 1216 additions & 26 deletions

File tree

openmetadata-dsl/pom.xml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.open-metadata</groupId>
9+
<artifactId>platform</artifactId>
10+
<version>1.12.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>openmetadata-dsl</artifactId>
14+
<name>OpenMetadata DSL</name>
15+
<description>
16+
OpenMetadata DSL (Domain-Specific Language) enables users to create sophisticated
17+
alert rules, governance policies, and automation workflows using natural, expressive syntax.
18+
</description>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>org.open-metadata</groupId>
23+
<artifactId>openmetadata-spec</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.open-metadata</groupId>
28+
<artifactId>openmetadata-service</artifactId>
29+
<version>${project.version}</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.antlr</groupId>
33+
<artifactId>antlr4-runtime</artifactId>
34+
<version>4.9.3</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework</groupId>
38+
<artifactId>spring-core</artifactId>
39+
<version>6.1.14</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>com.fasterxml.jackson.core</groupId>
43+
<artifactId>jackson-databind</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.slf4j</groupId>
47+
<artifactId>slf4j-api</artifactId>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.junit.jupiter</groupId>
51+
<artifactId>junit-jupiter</artifactId>
52+
<scope>test</scope>
53+
</dependency>
54+
</dependencies>
55+
56+
<build>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.apache.maven.plugins</groupId>
60+
<artifactId>maven-compiler-plugin</artifactId>
61+
</plugin>
62+
</plugins>
63+
</build>
64+
</project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2026 Collate
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
package org.openmetadata.dsl;
14+
15+
import java.util.List;
16+
import lombok.Builder;
17+
import lombok.Data;
18+
import org.openmetadata.schema.EntityInterface;
19+
import org.openmetadata.schema.type.ChangeEvent;
20+
21+
/**
22+
* DSL Evaluation Context - holds the data needed to evaluate DSL expressions.
23+
*/
24+
@Data
25+
@Builder
26+
public class DSLContext {
27+
28+
/** The entity being evaluated */
29+
private EntityInterface entity;
30+
31+
/** Change event if this is triggered by an entity change */
32+
private ChangeEvent changeEvent;
33+
34+
/** Additional variables defined in the expression */
35+
private List<DSLVariable> variables;
36+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright 2026 Collate
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
package org.openmetadata.dsl;
14+
15+
import lombok.extern.slf4j.Slf4j;
16+
17+
/**
18+
* Examples demonstrating OpenMetadata DSL usage.
19+
*/
20+
@Slf4j
21+
public class DSLExamples {
22+
23+
public static void main(String[] args) {
24+
log.info("=== OpenMetadata DSL Examples ===\n");
25+
26+
OpenMetadataDSL dsl = new OpenMetadataDSL();
27+
28+
// Example 1: Basic entity check
29+
exampleBasicEntityCheck(dsl);
30+
31+
// Example 2: Data quality monitoring
32+
exampleDataQualityMonitoring(dsl);
33+
34+
// Example 3: Compliance check
35+
exampleComplianceCheck(dsl);
36+
37+
// Example 4: Security monitoring
38+
exampleSecurityMonitoring(dsl);
39+
40+
log.info("\n=== Examples Complete ===");
41+
}
42+
43+
private static void exampleBasicEntityCheck(OpenMetadataDSL dsl) {
44+
log.info("--- Example 1: Basic Entity Check ---");
45+
46+
String rule = "entity.entityType == 'table' AND hasTag('Business-Critical')";
47+
48+
DSLContext context = DSLContext.builder()
49+
.entity(createMockEntity("users", "table"))
50+
.build();
51+
52+
boolean result = dsl.evaluateCondition(rule, context);
53+
log.info("Rule: {}", rule);
54+
log.info("Result: {}", result);
55+
log.info("");
56+
}
57+
58+
private static void exampleDataQualityMonitoring(OpenMetadataDSL dsl) {
59+
log.info("--- Example 2: Data Quality Monitoring ---");
60+
61+
String rule = "entity.entityType == 'table' " +
62+
"AND contains(entity.service.name, 'prod') " +
63+
"AND (isEmpty(entity.description) OR length(entity.owners) == 0)";
64+
65+
DSLContext context = DSLContext.builder()
66+
.entity(createMockEntity("orders", "table"))
67+
.build();
68+
69+
boolean result = dsl.evaluateCondition(rule, context);
70+
log.info("Rule: {}", rule);
71+
log.info("Result: {}", result);
72+
log.info("");
73+
}
74+
75+
private static void exampleComplianceCheck(OpenMetadataDSL dsl) {
76+
log.info("--- Example 3: Compliance & Governance ---");
77+
78+
String rule = "contains(entity.name, 'customer') " +
79+
"AND NOT hasTag('GDPR-Compliant') " +
80+
"AND entity.entityType == 'table'";
81+
82+
DSLContext context = DSLContext.builder()
83+
.entity(createMockEntity("customer_data", "table"))
84+
.build();
85+
86+
boolean result = dsl.evaluateCondition(rule, context);
87+
log.info("Rule: {}", rule);
88+
log.info("Result: {}", result);
89+
log.info("");
90+
}
91+
92+
private static void exampleSecurityMonitoring(OpenMetadataDSL dsl) {
93+
log.info("--- Example 4: Security Monitoring ---");
94+
95+
String rule = "contains(entity.name, 'ssn') " +
96+
"AND NOT hasTag('Security-Classified') " +
97+
"AND entity.entityType == 'table'";
98+
99+
DSLContext context = DSLContext.builder()
100+
.entity(createMockEntity("employee_ssn", "table"))
101+
.build();
102+
103+
boolean result = dsl.evaluateCondition(rule, context);
104+
log.info("Rule: {}", rule);
105+
log.info("Result: {}", result);
106+
log.info("");
107+
}
108+
109+
private static EntityInterface createMockEntity(String name, String type) {
110+
return new EntityInterface() {
111+
@Override
112+
public String getName() {
113+
return name;
114+
}
115+
116+
@Override
117+
public String getFullyQualifiedName() {
118+
return "prod." + name;
119+
}
120+
121+
@Override
122+
public String getDescription() {
123+
return null;
124+
}
125+
126+
@Override
127+
public org.openmetadata.schema.type.EntityReference getEntityReference() {
128+
return org.openmetadata.schema.type.EntityReference.builder()
129+
.id(java.util.UUID.randomUUID())
130+
.name(name)
131+
.type(type)
132+
.build();
133+
}
134+
135+
@Override
136+
public org.openmetadata.schema.type.EntityReference getService() {
137+
return org.openmetadata.schema.type.EntityReference.builder()
138+
.name("prod_mysql")
139+
.type("databaseService")
140+
.build();
141+
}
142+
143+
@Override
144+
public java.util.List<org.openmetadata.schema.type.TagLabel> getTags() {
145+
return java.util.List.of(
146+
org.openmetadata.schema.type.TagLabel.builder()
147+
.tagFQN("Business-Critical")
148+
.build()
149+
);
150+
}
151+
152+
@Override
153+
public java.util.List<org.openmetadata.schema.type.EntityReference> getOwners() {
154+
return null;
155+
}
156+
157+
@Override
158+
public Long getUpdatedAt() {
159+
return System.currentTimeMillis();
160+
}
161+
162+
@Override
163+
public String getId() {
164+
return java.util.UUID.randomUUID().toString();
165+
}
166+
167+
@Override
168+
public org.openmetadata.schema.type.EntityStatus getStatus() {
169+
return org.openmetadata.schema.type.EntityStatus.ACTIVE;
170+
}
171+
172+
@Override
173+
public org.openmetadata.schema.type.Include getInclude() {
174+
return org.openmetadata.schema.type.Include.ALL;
175+
}
176+
177+
@Override
178+
public java.util.Map<String, Object> getExtension() {
179+
return null;
180+
}
181+
182+
@Override
183+
public void setExtension(java.util.Map<String, Object> extension) {}
184+
185+
@Override
186+
public org.openmetadata.schema.type.ChangeDescription getChangeDescription() {
187+
return null;
188+
}
189+
190+
@Override
191+
public void setChangeDescription(org.openmetadata.schema.type.ChangeDescription changeDescription) {}
192+
193+
@Override
194+
public org.openmetadata.schema.type.Domain getDomain() {
195+
return null;
196+
}
197+
198+
@Override
199+
public void setDomain(org.openmetadata.schema.type.Domain domain) {}
200+
201+
@Override
202+
public java.util.List<org.openmetadata.schema.type.DataProduct> getDataProducts() {
203+
return null;
204+
}
205+
};
206+
}
207+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2026 Collate
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*/
13+
package org.openmetadata.dsl;
14+
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import lombok.Builder;
18+
import lombok.Data;
19+
20+
/**
21+
* Represents a parsed DSL expression node.
22+
*/
23+
@Data
24+
@Builder
25+
public class DSLExpression {
26+
27+
public enum ExpressionType {
28+
BOOLEAN,
29+
NUMBER,
30+
STRING,
31+
FIELD,
32+
FUNCTION,
33+
BINARY_EXPR,
34+
VARIABLE,
35+
CONDITIONAL
36+
}
37+
38+
private ExpressionType type;
39+
private Boolean booleanValue;
40+
private Number numberValue;
41+
private String stringValue;
42+
private String fieldName;
43+
private String functionName;
44+
private List<DSLExpression> arguments;
45+
private DSLExpression left;
46+
private DSLExpression right;
47+
private String operator;
48+
}

0 commit comments

Comments
 (0)