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

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new cba6505a4d [#9082] fix(core): validate job run id parsing to avoid 
SIOOBE (#9303)
cba6505a4d is described below

commit cba6505a4da228612a1a1e4b8b90224f3b88830c
Author: Hajun Yoo <[email protected]>
AuthorDate: Mon Dec 1 12:13:06 2025 +0900

    [#9082] fix(core): validate job run id parsing to avoid SIOOBE (#9303)
    
    ### What changes were proposed in this pull request?
    
    - Route job ID parsing in
    `JobMetaService`#`getJobByIdentifier`/`deleteJob` through
    `parseJobRunId` so malformed IDs throw `NoSuchEntityException` instead
    of runtime errors.
    - Fix the missing-job error message to include the actual identifier
    (`ident.toString()`).
    
    ### Why are the changes needed?
    
    - Prevents StringIndexOutOfBoundsException/NumberFormatException from
    malformed job IDs and ensures callers get a consistent
    NoSuchEntityException.
    
    Fix: #9082
    
    ### Does this PR introduce _any_ user-facing change?
    - No API or behavior changes. Malformed job IDs now consistently return
    `NoSuchEntityException` instead of unexpected runtime errors, improving
    error consistency.
    
    ### How was this patch tested?
     - Added unit tests:
    -
    
`TestJobMetaService`#`testGetJobWithMalformedIdentifierThrowsNoSuchEntityException`
    -
    
`TestJobMetaService`#`testDeleteJobWithMalformedIdentifierThrowsNoSuchEntityException`
      - Formatting: ./gradlew :core:spotlessJavaCheck
    - Unit: ./gradlew :core:test --tests
    "org.apache.gravitino.storage.relational.service.TestJobMetaService"
---
 .../storage/relational/service/JobMetaService.java | 33 ++++++++++++----------
 .../relational/service/TestJobMetaService.java     | 32 +++++++++++++++++++++
 2 files changed, 50 insertions(+), 15 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobMetaService.java
index 04de839247..3431976700 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobMetaService.java
@@ -85,13 +85,7 @@ public class JobMetaService {
       baseMetricName = "getJobByIdentifier")
   public JobEntity getJobByIdentifier(NameIdentifier ident) {
     String metalakeName = ident.namespace().level(0);
-    String jobRunId = ident.name();
-    long jobRunIdLong;
-    try {
-      jobRunIdLong = 
Long.parseLong(jobRunId.substring(JobHandle.JOB_ID_PREFIX.length()));
-    } catch (NumberFormatException e) {
-      throw new NoSuchEntityException("Invalid job run ID format %s", 
jobRunId);
-    }
+    long jobRunIdLong = parseJobRunId(ident.name());
 
     JobPO jobPO =
         SessionUtils.getWithoutCommit(
@@ -101,7 +95,7 @@ public class JobMetaService {
       throw new NoSuchEntityException(
           NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
           Entity.EntityType.JOB.name().toLowerCase(Locale.ROOT),
-          jobRunId);
+          ident.toString());
     }
     return JobPO.fromJobPO(jobPO, ident.namespace());
   }
@@ -133,13 +127,7 @@ public class JobMetaService {
 
   @Monitored(metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME, 
baseMetricName = "deleteJob")
   public boolean deleteJob(NameIdentifier jobIdent) {
-    String jobRunId = jobIdent.name();
-    long jobRunIdLong;
-    try {
-      jobRunIdLong = 
Long.parseLong(jobRunId.substring(JobHandle.JOB_ID_PREFIX.length()));
-    } catch (NumberFormatException e) {
-      throw new NoSuchEntityException("Invalid job run ID format %s", 
jobRunId);
-    }
+    long jobRunIdLong = parseJobRunId(jobIdent.name());
     int result =
         SessionUtils.doWithCommitAndFetchResult(
             JobMetaMapper.class, mapper -> 
mapper.softDeleteJobMetaByRunId(jobRunIdLong));
@@ -158,4 +146,19 @@ public class JobMetaService {
         JobMetaMapper.class,
         mapper -> mapper.deleteJobMetasByLegacyTimeline(legacyTimeline, 
limit));
   }
+
+  // Validate and parse a job run identifier of the form "job-<number>";
+  // throws NoSuchEntityException for any malformed input instead of leaking 
parsing errors.
+  private long parseJobRunId(String jobRunId) {
+    if (jobRunId == null
+        || !jobRunId.startsWith(JobHandle.JOB_ID_PREFIX)
+        || jobRunId.length() <= JobHandle.JOB_ID_PREFIX.length()) {
+      throw new NoSuchEntityException("Invalid job run ID format %s", 
jobRunId);
+    }
+    try {
+      return 
Long.parseLong(jobRunId.substring(JobHandle.JOB_ID_PREFIX.length()));
+    } catch (NumberFormatException e) {
+      throw new NoSuchEntityException("Invalid job run ID format %s", 
jobRunId);
+    }
+  }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobMetaService.java
index 30c5f31714..9136d5561f 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobMetaService.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestJobMetaService.java
@@ -35,6 +35,7 @@ import 
org.apache.gravitino.storage.relational.TestJDBCBackend;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.NamespaceUtil;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestTemplate;
 
 public class TestJobMetaService extends TestJDBCBackend {
@@ -225,4 +226,35 @@ public class TestJobMetaService extends TestJDBCBackend {
         JobMetaService.getInstance()
             .deleteJob(NameIdentifierUtil.ofJob(METALAKE_NAME, job.name())));
   }
+
+  @Test
+  public void testGetJobWithMalformedIdentifierThrowsNoSuchEntityException() {
+    Assertions.assertThrows(
+        NoSuchEntityException.class,
+        () ->
+            JobMetaService.getInstance()
+                .getJobByIdentifier(NameIdentifierUtil.ofJob(METALAKE_NAME, 
"invalid")));
+
+    Assertions.assertThrows(
+        NoSuchEntityException.class,
+        () ->
+            JobMetaService.getInstance()
+                .getJobByIdentifier(
+                    NameIdentifierUtil.ofJob(METALAKE_NAME, 
JobHandle.JOB_ID_PREFIX)));
+  }
+
+  @Test
+  public void 
testDeleteJobWithMalformedIdentifierThrowsNoSuchEntityException() {
+    Assertions.assertThrows(
+        NoSuchEntityException.class,
+        () ->
+            JobMetaService.getInstance()
+                .deleteJob(NameIdentifierUtil.ofJob(METALAKE_NAME, 
"invalid")));
+
+    Assertions.assertThrows(
+        NoSuchEntityException.class,
+        () ->
+            JobMetaService.getInstance()
+                .deleteJob(NameIdentifierUtil.ofJob(METALAKE_NAME, 
JobHandle.JOB_ID_PREFIX)));
+  }
 }

Reply via email to