This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch groovy-replace
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit e9a6c9d54984f0577e9211ad12e8bb7fd165942e
Author: Wu Sheng <[email protected]>
AuthorDate: Sat Feb 28 15:58:56 2026 +0800

    Add v2 runtime modules for MAL and LAL with manifest-based class loading 
(Phase 4)
    
    Introduces meter-analyzer-v2 and log-analyzer-v2 modules that provide
    same-FQCN replacement classes for DSL.java, Expression.java, and
    FilterExpression.java. The v2 classes load transpiled MalExpression/
    MalFilter/LalExpression implementations from META-INF manifests via
    Class.forName() instead of Groovy GroovyShell/ExpandoMetaClass/
    DelegatingScript. Uses maven-shade-plugin to overlay the upstream
    Groovy-dependent classes. Includes 7 unit tests.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 oap-server/analyzer/log-analyzer-v2/pom.xml        |  72 +++++++++++
 .../skywalking/oap/log/analyzer/dsl/DSL.java       | 142 +++++++++++++++++++++
 .../skywalking/oap/log/analyzer/dsl/DSLV2Test.java |  60 +++++++++
 oap-server/analyzer/meter-analyzer-v2/pom.xml      |  78 +++++++++++
 .../skywalking/oap/meter/analyzer/dsl/DSL.java     | 116 +++++++++++++++++
 .../oap/meter/analyzer/dsl/Expression.java         |  92 +++++++++++++
 .../oap/meter/analyzer/dsl/FilterExpression.java   | 113 ++++++++++++++++
 .../oap/meter/analyzer/dsl/DSLV2Test.java          |  98 ++++++++++++++
 oap-server/analyzer/pom.xml                        |   2 +
 9 files changed, 773 insertions(+)

diff --git a/oap-server/analyzer/log-analyzer-v2/pom.xml 
b/oap-server/analyzer/log-analyzer-v2/pom.xml
new file mode 100644
index 0000000000..6456f8209f
--- /dev/null
+++ b/oap-server/analyzer/log-analyzer-v2/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <parent>
+        <artifactId>analyzer</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>log-analyzer-v2</artifactId>
+    <description>Pure Java LAL runtime that loads transpiled LalExpression 
classes from manifest instead of Groovy</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>log-analyzer</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            
<createDependencyReducedPom>true</createDependencyReducedPom>
+                            <artifactSet>
+                                <includes>
+                                    
<include>org.apache.skywalking:log-analyzer</include>
+                                </includes>
+                            </artifactSet>
+                            <filters>
+                                <filter>
+                                    
<artifact>org.apache.skywalking:log-analyzer</artifact>
+                                    <excludes>
+                                        
<exclude>org/apache/skywalking/oap/log/analyzer/dsl/DSL.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/log/analyzer/dsl/DSL$*.class</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/oap-server/analyzer/log-analyzer-v2/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
 
b/oap-server/analyzer/log-analyzer-v2/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
new file mode 100644
index 0000000000..b3f4e4c977
--- /dev/null
+++ 
b/oap-server/analyzer/log-analyzer-v2/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/DSL.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.log.analyzer.dsl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.log.analyzer.dsl.spec.filter.FilterSpec;
+import org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+
+/**
+ * Same-FQCN replacement for upstream LAL DSL.
+ * Loads pre-compiled {@link LalExpression} classes from lal-expressions.txt 
manifest
+ * (keyed by SHA-256 hash) instead of Groovy {@code GroovyShell} runtime 
compilation.
+ */
+@Slf4j
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public class DSL {
+    private static final String MANIFEST_PATH = "META-INF/lal-expressions.txt";
+    private static volatile Map<String, String> EXPRESSION_MAP;
+    private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
+
+    private final LalExpression expression;
+    private final FilterSpec filterSpec;
+    private Binding binding;
+
+    public static DSL of(final ModuleManager moduleManager,
+                         final LogAnalyzerModuleConfig config,
+                         final String dsl) throws ModuleStartException {
+        final Map<String, String> exprMap = loadManifest();
+        final String dslHash = sha256(dsl);
+        final String className = exprMap.get(dslHash);
+        if (className == null) {
+            throw new ModuleStartException(
+                "Pre-compiled LAL expression not found for DSL hash: " + 
dslHash
+                    + ". Available: " + exprMap.size() + " expressions.");
+        }
+
+        try {
+            final Class<?> exprClass = Class.forName(className);
+            final LalExpression expression = (LalExpression) 
exprClass.getDeclaredConstructor().newInstance();
+            final FilterSpec filterSpec = new FilterSpec(moduleManager, 
config);
+            final int count = LOADED_COUNT.incrementAndGet();
+            log.debug("Loaded pre-compiled LAL expression [{}/{}]: {}", count, 
exprMap.size(), className);
+            return new DSL(expression, filterSpec);
+        } catch (ClassNotFoundException e) {
+            throw new ModuleStartException(
+                "Pre-compiled LAL expression class not found: " + className, 
e);
+        } catch (ReflectiveOperationException e) {
+            throw new ModuleStartException(
+                "Pre-compiled LAL expression instantiation failed: " + 
className, e);
+        }
+    }
+
+    public void bind(final Binding binding) {
+        this.binding = binding;
+        this.filterSpec.bind(binding);
+    }
+
+    public void evaluate() {
+        expression.execute(filterSpec, binding);
+    }
+
+    private static Map<String, String> loadManifest() {
+        if (EXPRESSION_MAP != null) {
+            return EXPRESSION_MAP;
+        }
+        synchronized (DSL.class) {
+            if (EXPRESSION_MAP != null) {
+                return EXPRESSION_MAP;
+            }
+            final Map<String, String> map = new HashMap<>();
+            try (InputStream is = 
DSL.class.getClassLoader().getResourceAsStream(MANIFEST_PATH)) {
+                if (is == null) {
+                    log.warn("LAL expression manifest not found: {}", 
MANIFEST_PATH);
+                    EXPRESSION_MAP = map;
+                    return map;
+                }
+                try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        line = line.trim();
+                        if (line.isEmpty()) {
+                            continue;
+                        }
+                        final String[] parts = line.split("=", 2);
+                        if (parts.length == 2) {
+                            map.put(parts[0], parts[1]);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to load LAL expression 
manifest", e);
+            }
+            log.info("Loaded {} pre-compiled LAL expressions from manifest", 
map.size());
+            EXPRESSION_MAP = map;
+            return map;
+        }
+    }
+
+    static String sha256(final String input) {
+        try {
+            final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            final byte[] hash = 
digest.digest(input.getBytes(StandardCharsets.UTF_8));
+            final StringBuilder hex = new StringBuilder();
+            for (final byte b : hash) {
+                hex.append(String.format("%02x", b));
+            }
+            return hex.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("SHA-256 not available", e);
+        }
+    }
+}
diff --git 
a/oap-server/analyzer/log-analyzer-v2/src/test/java/org/apache/skywalking/oap/log/analyzer/dsl/DSLV2Test.java
 
b/oap-server/analyzer/log-analyzer-v2/src/test/java/org/apache/skywalking/oap/log/analyzer/dsl/DSLV2Test.java
new file mode 100644
index 0000000000..a8f19d382e
--- /dev/null
+++ 
b/oap-server/analyzer/log-analyzer-v2/src/test/java/org/apache/skywalking/oap/log/analyzer/dsl/DSLV2Test.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.log.analyzer.dsl;
+
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DSLV2Test {
+
+    @Test
+    void ofThrowsWhenManifestMissing() {
+        // No META-INF/lal-expressions.txt on test classpath
+        assertThrows(ModuleStartException.class,
+            () -> DSL.of(null, null, "filter { json {} sink {} }"));
+    }
+
+    @Test
+    void sha256Deterministic() {
+        final String input = "filter { json {} sink {} }";
+        final String hash1 = DSL.sha256(input);
+        final String hash2 = DSL.sha256(input);
+        assertNotNull(hash1);
+        assertEquals(64, hash1.length());
+        assertEquals(hash1, hash2);
+    }
+
+    @Test
+    void sha256DifferentInputsDifferentHashes() {
+        final String hash1 = DSL.sha256("filter { json {} sink {} }");
+        final String hash2 = DSL.sha256("filter { text {} sink {} }");
+        assertNotNull(hash1);
+        assertNotNull(hash2);
+        assertNotEquals(hash1, hash2);
+    }
+
+    private static void assertNotEquals(final String a, final String b) {
+        if (a.equals(b)) {
+            throw new AssertionError("Expected different values but got: " + 
a);
+        }
+    }
+}
diff --git a/oap-server/analyzer/meter-analyzer-v2/pom.xml 
b/oap-server/analyzer/meter-analyzer-v2/pom.xml
new file mode 100644
index 0000000000..8bbcaa43ed
--- /dev/null
+++ b/oap-server/analyzer/meter-analyzer-v2/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <parent>
+        <artifactId>analyzer</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>meter-analyzer-v2</artifactId>
+    <description>Pure Java MAL runtime that loads transpiled 
MalExpression/MalFilter classes from manifests instead of Groovy</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>meter-analyzer</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            
<createDependencyReducedPom>true</createDependencyReducedPom>
+                            <artifactSet>
+                                <includes>
+                                    
<include>org.apache.skywalking:meter-analyzer</include>
+                                </includes>
+                            </artifactSet>
+                            <filters>
+                                <filter>
+                                    
<artifact>org.apache.skywalking:meter-analyzer</artifact>
+                                    <excludes>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/DSL.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/DSL$*.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/Expression.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/Expression$*.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression$*.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/NumberClosure.class</exclude>
+                                        
<exclude>org/apache/skywalking/oap/meter/analyzer/dsl/NumberClosure$*.class</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
new file mode 100644
index 0000000000..3098ac7672
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSL.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Same-FQCN replacement for upstream MAL DSL.
+ * Loads transpiled {@link MalExpression} classes from mal-expressions.txt 
manifest
+ * instead of Groovy {@code DelegatingScript} classes -- no Groovy runtime 
needed.
+ */
+@Slf4j
+public final class DSL {
+    private static final String MANIFEST_PATH = "META-INF/mal-expressions.txt";
+    private static volatile Map<String, String> SCRIPT_MAP;
+    private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
+
+    /**
+     * Parse string literal to Expression object, which can be reused.
+     *
+     * @param metricName the name of metric defined in mal rule
+     * @param expression string literal represents the DSL expression.
+     * @return Expression object could be executed.
+     */
+    public static Expression parse(final String metricName, final String 
expression) {
+        if (metricName == null) {
+            throw new UnsupportedOperationException(
+                "Init expressions (metricName=null) are not supported in v2 
mode. "
+                    + "All init expressions must be pre-compiled at build 
time.");
+        }
+
+        final Map<String, String> scriptMap = loadManifest();
+        final String className = scriptMap.get(metricName);
+        if (className == null) {
+            throw new IllegalStateException(
+                "Transpiled MAL expression not found for metric: " + metricName
+                    + ". Available: " + scriptMap.size() + " expressions");
+        }
+
+        try {
+            final Class<?> exprClass = Class.forName(className);
+            final MalExpression malExpr = (MalExpression) 
exprClass.getDeclaredConstructor().newInstance();
+            final int count = LOADED_COUNT.incrementAndGet();
+            log.debug("Loaded transpiled MAL expression [{}/{}]: {}", count, 
scriptMap.size(), metricName);
+            return new Expression(metricName, expression, malExpr);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException(
+                "Transpiled MAL expression class not found: " + className, e);
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(
+                "Failed to instantiate transpiled MAL expression: " + 
className, e);
+        }
+    }
+
+    private static Map<String, String> loadManifest() {
+        if (SCRIPT_MAP != null) {
+            return SCRIPT_MAP;
+        }
+        synchronized (DSL.class) {
+            if (SCRIPT_MAP != null) {
+                return SCRIPT_MAP;
+            }
+            final Map<String, String> map = new HashMap<>();
+            try (InputStream is = 
DSL.class.getClassLoader().getResourceAsStream(MANIFEST_PATH)) {
+                if (is == null) {
+                    log.warn("MAL expression manifest not found: {}", 
MANIFEST_PATH);
+                    SCRIPT_MAP = map;
+                    return map;
+                }
+                try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        line = line.trim();
+                        if (line.isEmpty()) {
+                            continue;
+                        }
+                        final String simpleName = 
line.substring(line.lastIndexOf('.') + 1);
+                        if (simpleName.startsWith("MalExpr_")) {
+                            final String metric = 
simpleName.substring("MalExpr_".length());
+                            map.put(metric, line);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to load MAL expression 
manifest", e);
+            }
+            log.info("Loaded {} transpiled MAL expressions from manifest", 
map.size());
+            SCRIPT_MAP = map;
+            return map;
+        }
+    }
+}
diff --git 
a/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/Expression.java
 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/Expression.java
new file mode 100644
index 0000000000..cf2e208301
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/Expression.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Same-FQCN replacement for upstream Expression.
+ * Wraps a transpiled {@link MalExpression} (pure Java) instead of a Groovy 
DelegatingScript.
+ * No ExpandoMetaClass, no propertyMissing(), no ThreadLocal sample repository.
+ */
+@Slf4j
+@ToString(of = {"literal"})
+public class Expression {
+
+    private final String metricName;
+    private final String literal;
+    private final MalExpression expression;
+
+    public Expression(final String metricName, final String literal, final 
MalExpression expression) {
+        this.metricName = metricName;
+        this.literal = literal;
+        this.expression = expression;
+    }
+
+    /**
+     * Parse the expression statically.
+     *
+     * @return Parsed context of the expression.
+     */
+    public ExpressionParsingContext parse() {
+        try (ExpressionParsingContext ctx = ExpressionParsingContext.create()) 
{
+            final Result r = run(ImmutableMap.of());
+            if (!r.isSuccess() && r.isThrowable()) {
+                throw new ExpressionParsingException(
+                    "failed to parse expression: " + literal + ", error:" + 
r.getError());
+            }
+            if (log.isDebugEnabled()) {
+                log.debug("\"{}\" is parsed", literal);
+            }
+            ctx.validate(literal);
+            return ctx;
+        }
+    }
+
+    /**
+     * Run the expression with a data map.
+     *
+     * @param sampleFamilies a data map includes all of candidates to be 
analysis.
+     * @return The result of execution.
+     */
+    public Result run(final Map<String, SampleFamily> sampleFamilies) {
+        try {
+            for (final SampleFamily s : sampleFamilies.values()) {
+                if (s != SampleFamily.EMPTY) {
+                    s.context.setMetricName(metricName);
+                }
+            }
+            final SampleFamily sf = expression.run(sampleFamilies);
+            if (sf == SampleFamily.EMPTY) {
+                if (ExpressionParsingContext.get().isEmpty()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("result of {} is empty by \"{}\"", 
sampleFamilies, literal);
+                    }
+                }
+                return Result.fail("Parsed result is an EMPTY sample family");
+            }
+            return Result.success(sf);
+        } catch (Throwable t) {
+            log.error("failed to run \"{}\"", literal, t);
+            return Result.fail(t);
+        }
+    }
+}
diff --git 
a/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
new file mode 100644
index 0000000000..b92318d9d3
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer-v2/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/FilterExpression.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Same-FQCN replacement for upstream FilterExpression.
+ * Loads transpiled {@link MalFilter} classes from 
mal-filter-expressions.properties
+ * manifest instead of Groovy filter closures -- no Groovy runtime needed.
+ */
+@Slf4j
+@ToString(of = {"literal"})
+public class FilterExpression {
+    private static final String MANIFEST_PATH = 
"META-INF/mal-filter-expressions.properties";
+    private static volatile Map<String, String> FILTER_MAP;
+    private static final AtomicInteger LOADED_COUNT = new AtomicInteger();
+
+    private final String literal;
+    private final MalFilter malFilter;
+
+    @SuppressWarnings("unchecked")
+    public FilterExpression(final String literal) {
+        this.literal = literal;
+
+        final Map<String, String> filterMap = loadManifest();
+        final String className = filterMap.get(literal);
+        if (className == null) {
+            throw new IllegalStateException(
+                "Transpiled MAL filter not found for: " + literal
+                    + ". Available filters: " + filterMap.size());
+        }
+
+        try {
+            final Class<?> filterClass = Class.forName(className);
+            malFilter = (MalFilter) 
filterClass.getDeclaredConstructor().newInstance();
+            final int count = LOADED_COUNT.incrementAndGet();
+            log.debug("Loaded transpiled MAL filter [{}/{}]: {}", count, 
filterMap.size(), literal);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException(
+                "Transpiled MAL filter class not found: " + className, e);
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(
+                "Failed to instantiate transpiled MAL filter: " + className, 
e);
+        }
+    }
+
+    public Map<String, SampleFamily> filter(final Map<String, SampleFamily> 
sampleFamilies) {
+        try {
+            final Map<String, SampleFamily> result = new HashMap<>();
+            for (final Map.Entry<String, SampleFamily> entry : 
sampleFamilies.entrySet()) {
+                final SampleFamily afterFilter = 
entry.getValue().filter(malFilter::test);
+                if (!Objects.equals(afterFilter, SampleFamily.EMPTY)) {
+                    result.put(entry.getKey(), afterFilter);
+                }
+            }
+            return result;
+        } catch (Throwable t) {
+            log.error("failed to run \"{}\"", literal, t);
+        }
+        return sampleFamilies;
+    }
+
+    private static Map<String, String> loadManifest() {
+        if (FILTER_MAP != null) {
+            return FILTER_MAP;
+        }
+        synchronized (FilterExpression.class) {
+            if (FILTER_MAP != null) {
+                return FILTER_MAP;
+            }
+            final Map<String, String> map = new HashMap<>();
+            try (InputStream is = 
FilterExpression.class.getClassLoader().getResourceAsStream(MANIFEST_PATH)) {
+                if (is == null) {
+                    log.warn("MAL filter manifest not found: {}", 
MANIFEST_PATH);
+                    FILTER_MAP = map;
+                    return map;
+                }
+                final Properties props = new Properties();
+                props.load(is);
+                props.forEach((k, v) -> map.put((String) k, (String) v));
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to load MAL filter 
manifest", e);
+            }
+            log.info("Loaded {} transpiled MAL filters from manifest", 
map.size());
+            FILTER_MAP = map;
+            return map;
+        }
+    }
+}
diff --git 
a/oap-server/analyzer/meter-analyzer-v2/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSLV2Test.java
 
b/oap-server/analyzer/meter-analyzer-v2/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSLV2Test.java
new file mode 100644
index 0000000000..d64847e771
--- /dev/null
+++ 
b/oap-server/analyzer/meter-analyzer-v2/src/test/java/org/apache/skywalking/oap/meter/analyzer/dsl/DSLV2Test.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.dsl;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DSLV2Test {
+
+    @Test
+    void parseRejectsNullMetricName() {
+        assertThrows(UnsupportedOperationException.class, () -> 
DSL.parse(null, "test_metric"));
+    }
+
+    @Test
+    void parseThrowsWhenManifestMissing() {
+        assertThrows(IllegalStateException.class, () -> 
DSL.parse("nonexistent_metric", "some_expr"));
+    }
+
+    @Test
+    void expressionRunWithMalExpression() {
+        final MalExpression simple = samples ->
+            samples.getOrDefault("test_metric", SampleFamily.EMPTY);
+
+        final Expression expr = new Expression("test_metric", "test_metric", 
simple);
+
+        // Run with empty map should return fail (EMPTY)
+        final Result emptyResult = expr.run(Map.of());
+        assertNotNull(emptyResult);
+        assertFalse(emptyResult.isSuccess());
+
+        // Run with a real sample should return success
+        final Sample sample = Sample.builder()
+            .name("test_metric")
+            .labels(ImmutableMap.of("service", "svc1"))
+            .value(42.0)
+            .timestamp(System.currentTimeMillis())
+            .build();
+        final SampleFamily sf = 
SampleFamily.build(SampleFamily.RunningContext.instance(), sample);
+        final Map<String, SampleFamily> sampleMap = new HashMap<>();
+        sampleMap.put("test_metric", sf);
+
+        final Result result = expr.run(sampleMap);
+        assertNotNull(result);
+        assertTrue(result.isSuccess());
+        assertEquals(sf, result.getData());
+    }
+
+    @Test
+    void filterExpressionWithMalFilter() {
+        final MalFilter filter = tags -> "svc1".equals(tags.get("service"));
+
+        final Sample sample1 = Sample.builder()
+            .name("metric")
+            .labels(ImmutableMap.of("service", "svc1"))
+            .value(10.0)
+            .timestamp(System.currentTimeMillis())
+            .build();
+        final Sample sample2 = Sample.builder()
+            .name("metric")
+            .labels(ImmutableMap.of("service", "svc2"))
+            .value(20.0)
+            .timestamp(System.currentTimeMillis())
+            .build();
+
+        final SampleFamily sf = SampleFamily.build(
+            SampleFamily.RunningContext.instance(), sample1, sample2);
+
+        final SampleFamily filtered = sf.filter(filter::test);
+        assertNotNull(filtered);
+        assertTrue(filtered != SampleFamily.EMPTY);
+        assertEquals(1, filtered.samples.length);
+        assertEquals(10.0, filtered.samples[0].getValue());
+    }
+}
diff --git a/oap-server/analyzer/pom.xml b/oap-server/analyzer/pom.xml
index 1c2a04f34d..5053d0e628 100644
--- a/oap-server/analyzer/pom.xml
+++ b/oap-server/analyzer/pom.xml
@@ -35,6 +35,8 @@
         <module>event-analyzer</module>
         <module>mal-transpiler</module>
         <module>lal-transpiler</module>
+        <module>meter-analyzer-v2</module>
+        <module>log-analyzer-v2</module>
     </modules>
 
     <dependencies>


Reply via email to