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)));
+ }
}