This is an automated email from the ASF dual-hosted git repository. jerryshao pushed a commit to branch branch-1.3 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 23189ae91af1a1c93965242710ea07a9b076d8f3 Author: MaSai <[email protected]> AuthorDate: Wed Sep 9 19:28:31 2026 +0800 [#12998] fix(catalogs): Preserve upstream error messages when wrapping exceptions (#12999) - Add `ExceptionMessages` helper to append the underlying cause message when wrapping exceptions. - Update catalog catch-all wraps (Kafka, Glue, Hive/HMS, Fileset, Model, Lance, Doris, Paimon, Hadoop FS) to use it. - Kafka: map invalid configuration on create/alter to `IllegalArgumentException` (4xx). - Glue: keep upstream text in default errors; map `AccessDeniedException` to `ForbiddenException`. - Add unit tests for `ExceptionMessages` and `GlueExceptionConverter`. Connectors were replacing actionable upstream errors with generic messages. Operators only saw the stack trace. Client-caused failures were also returned as 500. Fix: #12998 Yes. Failed catalog operations may return richer `message` text (upstream reason included). Some Kafka invalid-config and Glue access-denied failures map to 400/403 instead of 500. - Unit tests: `TestExceptionMessages`, `TestGlueExceptionConverter` - Suggested local run: `./gradlew spotlessApply :common:test --tests org.apache.gravitino.utils.TestExceptionMessages :catalogs:catalog-glue:test --tests org.apache.gravitino.catalog.glue.TestGlueExceptionConverter -PskipITs` Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <[email protected]> --- .../catalog/fileset/FilesetCatalogOperations.java | 38 ++--- .../catalog/glue/GlueExceptionConverter.java | 13 +- .../catalog/glue/GlueTableOperations.java | 11 +- .../catalog/glue/TestGlueExceptionConverter.java | 5 +- .../catalog/hive/HiveCatalogOperations.java | 3 +- .../catalog/hive/HiveTableOperations.java | 9 +- .../catalog/hive/HiveViewCatalogOperations.java | 19 +-- .../doris/operation/DorisTableOperations.java | 3 +- .../catalog/kafka/KafkaCatalogOperations.java | 70 ++++++---- .../generic/GenericCatalogOperations.java | 7 +- .../lakehouse/lance/LanceTableOperations.java | 24 ++-- .../lakehouse/paimon/utils/CatalogUtils.java | 3 +- .../catalog/model/ModelCatalogOperations.java | 29 ++-- .../catalog/hadoop/fs/FileSystemUtils.java | 7 +- .../catalog/hadoop/fs/HDFSFileSystemProxy.java | 5 +- .../gravitino/hive/client/HiveClientFactory.java | 5 +- .../gravitino/hive/client/HiveClientImpl.java | 5 +- .../hive/client/HiveExceptionConverter.java | 6 +- .../gravitino/hive/client/ProxyHiveClientImpl.java | 3 +- .../org/apache/gravitino/hive/client/Util.java | 3 +- .../gravitino/hive/kerberos/KerberosClient.java | 3 +- .../apache/gravitino/utils/ExceptionMessages.java | 155 +++++++++++++++++++++ .../gravitino/utils/TestExceptionMessages.java | 143 +++++++++++++++++++ 23 files changed, 457 insertions(+), 112 deletions(-) diff --git a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java index 7753ff6d42..ac75ec03c2 100644 --- a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java +++ b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogOperations.java @@ -39,6 +39,7 @@ import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UncheckedIOException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -116,6 +117,7 @@ import org.apache.gravitino.meta.SchemaEntity; import org.apache.gravitino.metrics.MetricsSystem; import org.apache.gravitino.metrics.source.FilesetCatalogMetricsSource; import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.FilesetUtil; import org.apache.gravitino.utils.NameIdentifierUtil; import org.apache.gravitino.utils.NamespaceUtil; @@ -347,7 +349,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations .map(f -> NameIdentifier.of(namespace, f.name())) .toArray(NameIdentifier[]::new); } catch (IOException e) { - throw new RuntimeException("Failed to list filesets under namespace " + namespace, e); + throw ExceptionMessages.wrap("Failed to list filesets under namespace " + namespace, e); } } @@ -369,7 +371,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException exception) { throw new NoSuchFilesetException(exception, FILESET_DOES_NOT_EXIST_MSG, ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to load fileset %s" + ident, ioe); + throw ExceptionMessages.wrap("Failed to load fileset %s" + ident, ioe); } } @@ -416,7 +418,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations .toArray(FileInfo[]::new); } catch (IOException e) { - throw new RuntimeException("Failed to list files in fileset" + filesetIdent, e); + throw ExceptionMessages.wrap("Failed to list files in fileset" + filesetIdent, e); } } @@ -444,7 +446,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations throw new FilesetAlreadyExistsException("Fileset %s already exists", ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to check if fileset " + ident + " exists", ioe); + throw ExceptionMessages.wrap("Failed to check if fileset " + ident + " exists", ioe); } SchemaEntity schemaEntity; @@ -454,7 +456,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException exception) { throw new NoSuchSchemaException(exception, SCHEMA_DOES_NOT_EXIST_MSG, schemaIdent); } catch (IOException ioe) { - throw new RuntimeException("Failed to load schema " + schemaIdent, ioe); + throw ExceptionMessages.wrap("Failed to load schema " + schemaIdent, ioe); } // For external fileset, the storageLocation must be set. @@ -557,7 +559,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations } } catch (IOException ioe) { - throw new RuntimeException("Failed to create fileset " + ident, ioe); + throw ExceptionMessages.wrap("Failed to create fileset " + ident, ioe); } } @@ -591,7 +593,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations try { store.put(filesetEntity, true /* overwrite */); } catch (IOException ioe) { - throw new RuntimeException("Failed to create fileset " + ident, ioe); + throw ExceptionMessages.wrap("Failed to create fileset " + ident, ioe); } return FilesetImpl.builder() @@ -654,7 +656,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations throw new NoSuchFilesetException(FILESET_DOES_NOT_EXIST_MSG, ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to load fileset " + ident, ioe); + throw ExceptionMessages.wrap("Failed to load fileset " + ident, ioe); } try { @@ -674,12 +676,12 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations .withAuditInfo(updatedFilesetEntity.auditInfo()) .build(); } catch (IOException ioe) { - throw new RuntimeException("Failed to update fileset " + ident, ioe); + throw ExceptionMessages.wrap("Failed to update fileset " + ident, ioe); } catch (NoSuchEntityException nsee) { throw new NoSuchFilesetException(nsee, FILESET_DOES_NOT_EXIST_MSG, ident); } catch (AlreadyExistsException aee) { // This is happened when renaming a fileset to an existing fileset name. - throw new RuntimeException( + throw ExceptionMessages.wrap( "Fileset with the same name " + ident.name() + " already exists", aee); } } @@ -735,8 +737,10 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException ne) { LOG.warn("Fileset {} does not exist", ident); return false; + } catch (UncheckedIOException uioe) { + throw ExceptionMessages.wrap("Failed to delete fileset " + ident, uioe.getCause()); } catch (IOException ioe) { - throw new RuntimeException("Failed to delete fileset " + ident, ioe); + throw ExceptionMessages.wrap("Failed to delete fileset " + ident, ioe); } } @@ -760,7 +764,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations throw new SchemaAlreadyExistsException("Schema %s already exists", ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to check if schema " + ident + " exists", ioe); + throw ExceptionMessages.wrap("Failed to check if schema " + ident + " exists", ioe); } Map<String, Path> schemaPaths = getAndCheckSchemaPaths(ident.name(), properties); @@ -804,7 +808,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations } } catch (IOException ioe) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to create schema " + ident + " location " + schemaPath, ioe); } } @@ -821,7 +825,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations throw new NoSuchSchemaException(SCHEMA_DOES_NOT_EXIST_MSG, ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to check if schema " + ident + " exists", ioe); + throw ExceptionMessages.wrap("Failed to check if schema " + ident + " exists", ioe); } // note: we need to invalidate the related fileset cache when the schema rename change is @@ -958,7 +962,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations LOG.warn("Schema {} does not exist", ident); return false; } catch (IOException ioe) { - throw new RuntimeException("Failed to delete schema " + ident + " location", ioe); + throw ExceptionMessages.wrap("Failed to delete schema " + ident + " location", ioe); } } @@ -1118,7 +1122,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations + locationName); } } catch (IOException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to check if fileset catalog location exists: " + v, e); } } @@ -1450,7 +1454,7 @@ public class FilesetCatalogOperations extends ManagedSchemaOperations "Interrupted when getting FileSystem for path: {}, possibly the server is" + " shutting down or catalog is been dropped", path); - throw new RuntimeException("Interrupted when getting FileSystem for path: " + path, e); + throw ExceptionMessages.wrap("Interrupted when getting FileSystem for path: " + path, e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) { diff --git a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java index 544726bf5b..4ab3ed9c9b 100644 --- a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java +++ b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java @@ -19,12 +19,15 @@ package org.apache.gravitino.catalog.glue; import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.exceptions.ForbiddenException; import org.apache.gravitino.exceptions.NoSuchSchemaException; import org.apache.gravitino.exceptions.NoSuchTableException; import org.apache.gravitino.exceptions.SchemaAlreadyExistsException; import org.apache.gravitino.exceptions.TableAlreadyExistsException; +import org.apache.gravitino.utils.ExceptionMessages; import software.amazon.awssdk.awscore.exception.AwsErrorDetails; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.services.glue.model.AccessDeniedException; import software.amazon.awssdk.services.glue.model.AlreadyExistsException; import software.amazon.awssdk.services.glue.model.EntityNotFoundException; import software.amazon.awssdk.services.glue.model.GlueException; @@ -84,7 +87,10 @@ final class GlueExceptionConverter { return new SchemaAlreadyExistsException(e, "%s already exists", context); } if (e instanceof InvalidInputException) { - return new IllegalArgumentException(context + ": " + e.getMessage(), e); + return ExceptionMessages.illegalArgument(context, e); + } + if (e instanceof AccessDeniedException) { + return new ForbiddenException(e, "Glue error: %s: %s", context, awsErrorDetail(e)); } return new RuntimeException("Glue error: " + context + ": " + awsErrorDetail(e), e); } @@ -104,7 +110,10 @@ final class GlueExceptionConverter { return new TableAlreadyExistsException(e, "%s already exists", context); } if (e instanceof InvalidInputException) { - return new IllegalArgumentException(context + ": " + e.getMessage(), e); + return ExceptionMessages.illegalArgument(context, e); + } + if (e instanceof AccessDeniedException) { + return new ForbiddenException(e, "Glue error: %s: %s", context, awsErrorDetail(e)); } return new RuntimeException("Glue error: " + context + ": " + awsErrorDetail(e), e); } diff --git a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java index 4891932f29..7b5af82f49 100644 --- a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java +++ b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueTableOperations.java @@ -32,6 +32,7 @@ import org.apache.gravitino.rel.expressions.literals.Literals; import org.apache.gravitino.rel.partitions.IdentityPartition; import org.apache.gravitino.rel.partitions.Partition; import org.apache.gravitino.rel.partitions.Partitions; +import org.apache.gravitino.utils.ExceptionMessages; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.glue.GlueClient; @@ -99,7 +100,7 @@ class GlueTableOperations implements TableOperations, SupportsPartitions { nextToken = resp.nextToken(); } while (nextToken != null); } catch (GlueException e) { - throw new RuntimeException("Failed to list partitions for table " + tableName, e); + throw ExceptionMessages.wrap("Failed to list partitions for table " + tableName, e); } return names.toArray(new String[0]); } @@ -121,7 +122,7 @@ class GlueTableOperations implements TableOperations, SupportsPartitions { nextToken = resp.nextToken(); } while (nextToken != null); } catch (GlueException e) { - throw new RuntimeException("Failed to list partitions for table " + tableName, e); + throw ExceptionMessages.wrap("Failed to list partitions for table " + tableName, e); } return partitions.toArray(new Partition[0]); } @@ -141,7 +142,7 @@ class GlueTableOperations implements TableOperations, SupportsPartitions { throw new NoSuchPartitionException( e, "Partition %s does not exist in table %s", partitionName, tableName); } catch (GlueException e) { - throw new RuntimeException("Failed to get partition " + partitionName, e); + throw ExceptionMessages.wrap("Failed to get partition " + partitionName, e); } } @@ -181,7 +182,7 @@ class GlueTableOperations implements TableOperations, SupportsPartitions { throw new PartitionAlreadyExistsException( e, "Partition %s already exists in table %s", partition.name(), tableName); } catch (GlueException e) { - throw new RuntimeException("Failed to add partition " + partition.name(), e); + throw ExceptionMessages.wrap("Failed to add partition " + partition.name(), e); } LOG.info("Added partition {} to {}.{}", partition.name(), dbName, tableName); @@ -210,7 +211,7 @@ class GlueTableOperations implements TableOperations, SupportsPartitions { } catch (EntityNotFoundException e) { return false; } catch (GlueException e) { - throw new RuntimeException("Failed to drop partition " + partitionName, e); + throw ExceptionMessages.wrap("Failed to drop partition " + partitionName, e); } } diff --git a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java index 33ff92a56e..23a32114a7 100644 --- a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java +++ b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.apache.gravitino.exceptions.ForbiddenException; import org.apache.gravitino.exceptions.NoSuchSchemaException; import org.apache.gravitino.exceptions.NoSuchTableException; import org.apache.gravitino.exceptions.SchemaAlreadyExistsException; @@ -97,7 +98,7 @@ public class TestGlueExceptionConverter { RuntimeException converted = GlueExceptionConverter.toSchemaException(e, "schema drop_me"); - assertEquals(RuntimeException.class, converted.getClass()); + assertInstanceOf(ForbiddenException.class, converted); assertSame(e, converted.getCause()); String message = converted.getMessage(); assertTrue(message.contains("schema drop_me"), message); @@ -120,7 +121,7 @@ public class TestGlueExceptionConverter { RuntimeException converted = GlueExceptionConverter.toTableException(e, "table ctas_test"); - assertEquals(RuntimeException.class, converted.getClass()); + assertInstanceOf(ForbiddenException.class, converted); assertSame(e, converted.getCause()); String message = converted.getMessage(); assertTrue(message.contains("table ctas_test"), message); diff --git a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java index cd73a9eed4..c3751a6c37 100644 --- a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java +++ b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java @@ -89,6 +89,7 @@ import org.apache.gravitino.rel.expressions.transforms.Transforms; import org.apache.gravitino.rel.indexes.Index; import org.apache.gravitino.rel.types.Type; import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.PrincipalUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -493,7 +494,7 @@ public class HiveCatalogOperations return new HiveTableHandle(table, clientPool); } catch (InterruptedException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to load Hive table " + tableIdent.name() + " from Hive metastore", e); } } diff --git a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java index 5ba130a8f3..bf78f57f3e 100644 --- a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java +++ b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveTableOperations.java @@ -36,6 +36,7 @@ import org.apache.gravitino.hive.HiveTable; import org.apache.gravitino.rel.SupportsPartitions; import org.apache.gravitino.rel.partitions.IdentityPartition; import org.apache.gravitino.rel.partitions.Partition; +import org.apache.gravitino.utils.ExceptionMessages; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +57,7 @@ public class HiveTableOperations implements TableOperations, SupportsPartitions .clientPool() .run(c -> c.listPartitionNames(tableHandle.table(), (short) -1).toArray(new String[0])); } catch (InterruptedException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to list partition names of table " + tableHandle.name() + "from Hive Metastore", e); } @@ -70,7 +71,7 @@ public class HiveTableOperations implements TableOperations, SupportsPartitions .run(c -> c.listPartitions(tableHandle.table(), (short) -1)) .toArray(new Partition[0]); } catch (InterruptedException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to list partitions of table " + tableHandle.name() + "from Hive Metastore", e); } } @@ -81,7 +82,7 @@ public class HiveTableOperations implements TableOperations, SupportsPartitions return tableHandle.clientPool().run(c -> c.getPartition(tableHandle.table(), partitionName)); } catch (InterruptedException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to get partition " + partitionName + " of table " @@ -172,7 +173,7 @@ public class HiveTableOperations implements TableOperations, SupportsPartitions return false; } catch (InterruptedException e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to get partition " + partitionName + " of table " diff --git a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java index f3d078ff2e..f571fc4eea 100644 --- a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java +++ b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveViewCatalogOperations.java @@ -50,6 +50,7 @@ import org.apache.gravitino.rel.SQLRepresentation; import org.apache.gravitino.rel.View; import org.apache.gravitino.rel.ViewCatalog; import org.apache.gravitino.rel.ViewChange; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.PrincipalUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,7 +91,7 @@ class HiveViewCatalogOperations implements ViewCatalog { .map(name -> NameIdentifier.of(namespace, name)) .toArray(NameIdentifier[]::new); } catch (InterruptedException e) { - throw new RuntimeException("Failed to list Hive views in " + namespace, e); + throw ExceptionMessages.wrap("Failed to list Hive views in " + namespace, e); } } @@ -157,9 +158,9 @@ class HiveViewCatalogOperations implements ViewCatalog { } catch (TableAlreadyExistsException e) { throw new ViewAlreadyExistsException(e, "View %s already exists in Hive Metastore", ident); } catch (InterruptedException e) { - throw new RuntimeException("Failed to create Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to create Hive view " + ident, e); } catch (Exception e) { - throw new RuntimeException("Failed to create Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to create Hive view " + ident, e); } } @@ -264,9 +265,9 @@ class HiveViewCatalogOperations implements ViewCatalog { } catch (UnsupportedOperationException e) { throw e; } catch (InterruptedException e) { - throw new RuntimeException("Failed to alter Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to alter Hive view " + ident, e); } catch (Exception e) { - throw new RuntimeException("Failed to alter Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to alter Hive view " + ident, e); } } @@ -311,9 +312,9 @@ class HiveViewCatalogOperations implements ViewCatalog { } catch (NoSuchTableException e) { return false; } catch (InterruptedException e) { - throw new RuntimeException("Failed to drop Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to drop Hive view " + ident, e); } catch (Exception e) { - throw new RuntimeException("Failed to drop Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to drop Hive view " + ident, e); } } @@ -349,9 +350,9 @@ class HiveViewCatalogOperations implements ViewCatalog { } catch (NoSuchTableException e) { throw new NoSuchViewException(e, "View %s does not exist in Hive Metastore", ident); } catch (InterruptedException e) { - throw new RuntimeException("Failed to load Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to load Hive view " + ident, e); } catch (Exception e) { - throw new RuntimeException("Failed to load Hive view " + ident, e); + throw ExceptionMessages.wrap("Failed to load Hive view " + ident, e); } } diff --git a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java index b002af801b..8d0bf8464a 100644 --- a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java +++ b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java @@ -70,6 +70,7 @@ import org.apache.gravitino.rel.indexes.Index; import org.apache.gravitino.rel.indexes.Indexes; import org.apache.gravitino.rel.partitions.ListPartition; import org.apache.gravitino.rel.partitions.RangePartition; +import org.apache.gravitino.utils.ExceptionMessages; /** Table operations for Apache Doris. */ public class DorisTableOperations extends JdbcTableOperations { @@ -215,7 +216,7 @@ public class DorisTableOperations extends JdbcTableOperations { .toString()); } } catch (Exception e) { - throw new RuntimeException("Failed to get the number of backend servers", e); + throw ExceptionMessages.wrap("Failed to get the number of backend servers", e); } } diff --git a/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java b/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java index 980b92b542..85a5feaa29 100644 --- a/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java +++ b/catalogs/catalog-kafka/src/main/java/org/apache/gravitino/catalog/kafka/KafkaCatalogOperations.java @@ -66,6 +66,7 @@ import org.apache.gravitino.messaging.TopicChange; import org.apache.gravitino.meta.AuditInfo; import org.apache.gravitino.meta.SchemaEntity; import org.apache.gravitino.storage.IdGenerator; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.NamespaceUtil; import org.apache.gravitino.utils.PrincipalUtils; import org.apache.kafka.clients.admin.AdminClient; @@ -153,10 +154,9 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema adminClient = AdminClient.create(adminClientConfig); } catch (KafkaException e) { if (e.getCause() instanceof ConfigException) { - throw new IllegalArgumentException( - "Invalid configuration for Kafka AdminClient: " + e.getCause().getMessage(), e); + throw ExceptionMessages.illegalArgument("Invalid configuration for Kafka AdminClient", e); } - throw new RuntimeException("Failed to create Kafka AdminClient", e); + throw ExceptionMessages.wrap("Failed to create Kafka AdminClient", e); } createDefaultSchemaIfNecessary(); } @@ -173,13 +173,9 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema .map(name -> NameIdentifier.of(namespace, name)) .toArray(NameIdentifier[]::new); } catch (ExecutionException e) { - throw new RuntimeException( - String.format( - "Failed to list topics under the schema %s: %s", - namespace, e.getCause().getMessage()), - e); + throw ExceptionMessages.wrap("Failed to list topics under the schema " + namespace, e); } catch (InterruptedException e) { - throw new RuntimeException("Failed to list topics under the schema " + namespace, e); + throw ExceptionMessages.wrap("Failed to list topics under the schema " + namespace, e); } } @@ -226,10 +222,10 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema if (e.getCause() instanceof UnknownTopicOrPartitionException) { throw new NoSuchTopicException(e, "Topic %s does not exist", ident); } else { - throw new RuntimeException("Failed to load topic " + ident.name() + " from Kafka", e); + throw ExceptionMessages.wrap("Failed to load topic " + ident.name() + " from Kafka", e); } } catch (InterruptedException e) { - throw new RuntimeException("Failed to load topic " + ident.name() + " from Kafka", e); + throw ExceptionMessages.wrap("Failed to load topic " + ident.name() + " from Kafka", e); } LOG.info("Loaded topic {} from Kafka", ident); @@ -297,18 +293,16 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema throw new TopicAlreadyExistsException(e, "Topic %s already exists", ident); } else if (e.getCause() instanceof InvalidReplicationFactorException) { - throw new IllegalArgumentException( - "Invalid replication factor for topic " + ident + e.getCause().getMessage(), e); + throw ExceptionMessages.illegalArgument("Invalid replication factor for topic " + ident, e); } else if (e.getCause() instanceof InvalidConfigurationException) { - throw new IllegalArgumentException( - "Invalid properties for topic " + ident + e.getCause().getMessage(), e); + throw ExceptionMessages.illegalArgument("Invalid properties for topic " + ident, e); } else { - throw new RuntimeException("Failed to create topic in Kafka" + ident, e); + throw ExceptionMessages.wrap("Failed to create topic in Kafka " + ident, e); } } catch (InterruptedException e) { - throw new RuntimeException("Failed to create topic in Kafka" + ident, e); + throw ExceptionMessages.wrap("Failed to create topic in Kafka " + ident, e); } } @@ -381,10 +375,10 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema if (e.getCause() instanceof UnknownTopicOrPartitionException) { return false; } else { - throw new RuntimeException("Failed to drop topic " + ident.name() + " from Kafka", e); + throw ExceptionMessages.wrap("Failed to drop topic " + ident.name() + " from Kafka", e); } } catch (InterruptedException e) { - throw new RuntimeException("Failed to drop topic " + ident.name() + " from Kafka", e); + throw ExceptionMessages.wrap("Failed to drop topic " + ident.name() + " from Kafka", e); } } @@ -397,7 +391,7 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema .map(s -> NameIdentifier.of(namespace, s.name())) .toArray(NameIdentifier[]::new); } catch (IOException e) { - throw new RuntimeException("Failed to list schemas under namespace " + namespace, e); + throw ExceptionMessages.wrap("Failed to list schemas under namespace " + namespace, e); } } @@ -426,7 +420,7 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema } catch (NoSuchEntityException exception) { throw new NoSuchSchemaException(exception, "Schema %s does not exist", ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to load schema " + ident, ioe); + throw ExceptionMessages.wrap("Failed to load schema " + ident, ioe); } } @@ -534,8 +528,16 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema Collections.singletonMap(topicName, NewPartitions.increaseTo(newPartitionCount))) .all() .get(); - } catch (Exception e) { - throw new RuntimeException("Failed to increase partition count for topic " + topicName, e); + } catch (ExecutionException e) { + if (e.getCause() instanceof InvalidConfigurationException + || e.getCause() instanceof IllegalArgumentException) { + throw ExceptionMessages.illegalArgument( + "Failed to increase partition count for topic " + topicName, e); + } + throw ExceptionMessages.wrap("Failed to increase partition count for topic " + topicName, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw ExceptionMessages.wrap("Failed to increase partition count for topic " + topicName, e); } } @@ -546,10 +548,19 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema .incrementalAlterConfigs(Collections.singletonMap(topicResource, alterConfigOps)) .all() .get(); - } catch (UnknownTopicOrPartitionException e) { - throw new NoSuchTopicException(e, "Topic %s does not exist", topicName); - } catch (Exception e) { - throw new RuntimeException("Failed to alter topic properties for topic " + topicName, e); + } catch (ExecutionException e) { + if (e.getCause() instanceof UnknownTopicOrPartitionException) { + throw new NoSuchTopicException(e, "Topic %s does not exist", topicName); + } + if (e.getCause() instanceof InvalidConfigurationException + || e.getCause() instanceof IllegalArgumentException) { + throw ExceptionMessages.illegalArgument( + "Failed to alter topic properties for topic " + topicName, e); + } + throw ExceptionMessages.wrap("Failed to alter topic properties for topic " + topicName, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw ExceptionMessages.wrap("Failed to alter topic properties for topic " + topicName, e); } } @@ -590,7 +601,8 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema return; } } catch (IOException e) { - throw new RuntimeException("Failed to check if schema " + defaultSchemaIdent + " exists", e); + throw ExceptionMessages.wrap( + "Failed to check if schema " + defaultSchemaIdent + " exists", e); } // Create the default schema @@ -617,7 +629,7 @@ public class KafkaCatalogOperations implements CatalogOperations, SupportsSchema try { store.put(defaultSchema, true /* overwrite */); } catch (IOException ioe) { - throw new RuntimeException("Failed to create default schema for Kafka catalog", ioe); + throw ExceptionMessages.wrap("Failed to create default schema for Kafka catalog", ioe); } } } diff --git a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java index 445b87dcc4..11497e4d68 100644 --- a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java +++ b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/generic/GenericCatalogOperations.java @@ -66,6 +66,7 @@ import org.apache.gravitino.rel.expressions.sorts.SortOrder; import org.apache.gravitino.rel.expressions.transforms.Transform; import org.apache.gravitino.rel.indexes.Index; import org.apache.gravitino.storage.IdGenerator; +import org.apache.gravitino.utils.ExceptionMessages; /** Operations for interacting with a generic lakehouse catalog in Apache Gravitino. */ public class GenericCatalogOperations implements CatalogOperations, SupportsSchemas, TableCatalog { @@ -352,11 +353,9 @@ public class GenericCatalogOperations implements CatalogOperations, SupportsSche } else if (t instanceof IllegalArgumentException) { throw (IllegalArgumentException) t; } else if (t instanceof IOException) { - throw new RuntimeException( - String.format("Failed to load table %s: %s", tableIdent, t.getMessage()), t); + throw ExceptionMessages.wrap("Failed to load table " + tableIdent, t); } else { - throw new RuntimeException( - String.format("Unexpected exception when loading table %s", tableIdent), t); + throw ExceptionMessages.wrap("Unexpected exception when loading table " + tableIdent, t); } } } diff --git a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java index dd7dc2b314..fb2bfaba1f 100644 --- a/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java +++ b/catalogs/catalog-lakehouse-generic/src/main/java/org/apache/gravitino/catalog/lakehouse/lance/LanceTableOperations.java @@ -66,6 +66,7 @@ import org.apache.gravitino.rel.expressions.transforms.Transform; import org.apache.gravitino.rel.indexes.Index; import org.apache.gravitino.storage.IdGenerator; import org.apache.gravitino.storage.relational.service.TableMetaService; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.PrincipalUtils; import org.lance.Dataset; import org.lance.ReadOptions; @@ -303,7 +304,7 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (NoSuchTableException e) { return false; } catch (Exception e) { - throw new RuntimeException("Failed to purge Lance dataset for table " + ident, e); + throw ExceptionMessages.wrap("Failed to purge Lance dataset for table " + ident, e); } } @@ -337,7 +338,7 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (NoSuchTableException e) { return false; } catch (Exception e) { - throw new RuntimeException("Failed to drop Lance dataset for table " + ident, e); + throw ExceptionMessages.wrap("Failed to drop Lance dataset for table " + ident, e); } } @@ -355,7 +356,7 @@ public class LanceTableOperations extends ManagedTableOperations { && e.getMessage().contains("Not found:")) { LOG.warn("Lance dataset at {} was already deleted, skipping.", location); } else { - throw new RuntimeException("Failed to delete Lance dataset at " + location, e); + throw ExceptionMessages.wrap("Failed to delete Lance dataset at " + location, e); } } } @@ -433,7 +434,7 @@ public class LanceTableOperations extends ManagedTableOperations { } throw e; } catch (Exception e) { - throw new RuntimeException("Failed to create Lance dataset at location " + location, e); + throw ExceptionMessages.wrap("Failed to create Lance dataset at location " + location, e); } } @@ -491,7 +492,9 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (Exception e) { if (forAlter) { throw new IllegalStateException( - "Failed to load Lance schema before altering table " + ident, e); + ExceptionMessages.withCause( + "Failed to load Lance schema before altering table " + ident, e), + e); } LOG.debug( "Failed to load Lance schema from location {} for table {}. Return stored metadata.", @@ -577,9 +580,9 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (NoSuchEntityException e) { throw new NoSuchTableException(e, "Table %s does not exist", ident); } catch (EntityAlreadyExistsException e) { - throw new IllegalArgumentException("Failed to repair table " + ident, e); + throw ExceptionMessages.illegalArgument("Failed to repair table " + ident, e); } catch (IOException e) { - throw new RuntimeException("Failed to repair table " + ident, e); + throw ExceptionMessages.wrap("Failed to repair table " + ident, e); } } @@ -688,9 +691,10 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (NoSuchEntityException e) { throw new NoSuchTableException(e, "Table %s does not exist", ident); } catch (EntityAlreadyExistsException e) { - throw new IllegalArgumentException("Failed to record empty version for table " + ident, e); + throw ExceptionMessages.illegalArgument( + "Failed to record empty version for table " + ident, e); } catch (IOException e) { - throw new RuntimeException("Failed to record empty version for table " + ident, e); + throw ExceptionMessages.wrap("Failed to record empty version for table " + ident, e); } } @@ -815,7 +819,7 @@ public class LanceTableOperations extends ManagedTableOperations { } catch (RuntimeException e) { throw e; } catch (Exception e) { - throw new RuntimeException( + throw ExceptionMessages.wrap( "Failed to handle alterations to Lance dataset at location " + location, e); } } diff --git a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java index be3febd7d5..dfe5ac749e 100644 --- a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java +++ b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/utils/CatalogUtils.java @@ -41,6 +41,7 @@ import org.apache.gravitino.catalog.lakehouse.paimon.authentication.Authenticati import org.apache.gravitino.catalog.lakehouse.paimon.authentication.kerberos.KerberosClient; import org.apache.gravitino.catalog.lakehouse.paimon.ops.PaimonBackendCatalogWrapper; import org.apache.gravitino.exceptions.ConnectionFailedException; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.conf.Configuration; import org.apache.paimon.catalog.Catalog; import org.apache.paimon.catalog.CatalogContext; @@ -71,7 +72,7 @@ public class CatalogUtils { Catalog catalog = loadCatalogBackendWithKerberosAuth(paimonConfig, configuration); return new PaimonBackendCatalogWrapper(catalog, kerberosClient); } catch (Exception e) { - throw new RuntimeException("Failed to login with kerberos", e); + throw ExceptionMessages.wrap("Failed to login with kerberos", e); } } else { throw new UnsupportedOperationException( diff --git a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java index 1f2d14c6e0..1d0c232366 100644 --- a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java +++ b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java @@ -54,6 +54,7 @@ import org.apache.gravitino.model.ModelCatalog; import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.model.ModelVersionChange; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.NameIdentifierUtil; import org.apache.gravitino.utils.NamespaceUtil; import org.apache.gravitino.utils.PrincipalUtils; @@ -101,7 +102,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException e) { throw new NoSuchSchemaException(e, "Schema %s does not exist", namespace); } catch (IOException ioe) { - throw new RuntimeException("Failed to list models under namespace " + namespace, ioe); + throw ExceptionMessages.wrap("Failed to list models under namespace " + namespace, ioe); } } @@ -116,7 +117,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException e) { throw new NoSuchModelException(e, "Model %s does not exist", ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to get model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to get model " + ident, ioe); } } @@ -146,7 +147,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations try { store.put(model, false /* overwrite */); } catch (IOException e) { - throw new RuntimeException("Failed to register model " + ident, e); + throw ExceptionMessages.wrap("Failed to register model " + ident, e); } catch (EntityAlreadyExistsException e) { throw new ModelAlreadyExistsException(e, "Model %s already exists", ident); } catch (NoSuchEntityException e) { @@ -163,7 +164,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations try { return store.delete(ident, Entity.EntityType.MODEL); } catch (IOException ioe) { - throw new RuntimeException("Failed to delete model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to delete model " + ident, ioe); } } @@ -180,7 +181,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException e) { throw new NoSuchModelException(e, "Model %s does not exist", ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to list model versions for model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to list model versions for model " + ident, ioe); } } @@ -197,7 +198,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException e) { throw new NoSuchModelException(e, "Model %s does not exist", ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to list model version infos for model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to list model version infos for model " + ident, ioe); } } @@ -254,7 +255,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations try { store.put(modelVersion, false /* overwrite */); } catch (IOException e) { - throw new RuntimeException("Failed to link model version " + ident, e); + throw ExceptionMessages.wrap("Failed to link model version " + ident, e); } catch (EntityAlreadyExistsException e) { throw new ModelVersionAliasesAlreadyExistException( e, "Model version alias already exists in %s", ident); @@ -306,7 +307,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations throw new NoSuchModelException("Model %s does not exist", ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to alter model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to alter model " + ident, ioe); } try { @@ -320,12 +321,12 @@ public class ModelCatalogOperations extends ManagedSchemaOperations return toModelImpl(updatedModelEntity); } catch (IOException ioe) { - throw new RuntimeException("Failed to load model " + ident, ioe); + throw ExceptionMessages.wrap("Failed to load model " + ident, ioe); } catch (NoSuchEntityException nsee) { throw new NoSuchModelException(nsee, "Model %s does not exist", ident); } catch (EntityAlreadyExistsException eaee) { // This is happened when renaming a model to an existing model name. - throw new RuntimeException("Model already exist " + ident.name(), eaee); + throw ExceptionMessages.wrap("Model already exist " + ident.name(), eaee); } } @@ -416,7 +417,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations throw new NoSuchModelVersionException("Model version %s does not exist", ident); } } catch (IOException ioe) { - throw new RuntimeException("Failed to alter model version " + ident, ioe); + throw ExceptionMessages.wrap("Failed to alter model version " + ident, ioe); } try { @@ -430,7 +431,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations return toModelVersionImpl(updatedModelVersionEntity); } catch (IOException ioe) { - throw new RuntimeException("Failed to load model version " + ident, ioe); + throw ExceptionMessages.wrap("Failed to load model version " + ident, ioe); } catch (NoSuchEntityException nsee) { throw new NoSuchModelVersionException(nsee, "Model Version %s does not exist", ident); } @@ -546,7 +547,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations } catch (NoSuchEntityException e) { throw new NoSuchModelVersionException(e, "Model version %s does not exist", ident); } catch (IOException ioe) { - throw new RuntimeException("Failed to get model version " + ident, ioe); + throw ExceptionMessages.wrap("Failed to get model version " + ident, ioe); } } @@ -597,7 +598,7 @@ public class ModelCatalogOperations extends ManagedSchemaOperations try { return store.delete(ident, Entity.EntityType.MODEL_VERSION); } catch (IOException ioe) { - throw new RuntimeException("Failed to delete model version " + ident, ioe); + throw ExceptionMessages.wrap("Failed to delete model version " + ident, ioe); } } diff --git a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java index 0c1b00cbee..10265761d5 100644 --- a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java +++ b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/FileSystemUtils.java @@ -32,6 +32,7 @@ import java.util.ServiceLoader; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; @@ -158,7 +159,7 @@ public class FileSystemUtils { gravitinoFileSystemCredentialsProvider.setConf(conf); return gravitinoFileSystemCredentialsProvider; } catch (Exception e) { - throw new RuntimeException("Failed to create GravitinoFileSystemCredentialProvider", e); + throw ExceptionMessages.wrap("Failed to create GravitinoFileSystemCredentialProvider", e); } } @@ -220,7 +221,7 @@ public class FileSystemUtils { return configuration; } catch (Exception e) { - throw new RuntimeException("Failed to create configuration", e); + throw ExceptionMessages.wrap("Failed to create configuration", e); } } @@ -231,7 +232,7 @@ public class FileSystemUtils { writeElement(writer, VALUE_TAG, value); writer.writeEndElement(); } catch (Exception e) { - throw new RuntimeException("Failed to write property: " + key, e); + throw ExceptionMessages.wrap("Failed to write property: " + key, e); } } diff --git a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java index cf27d04903..2b26356dae 100644 --- a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java +++ b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/HDFSFileSystemProxy.java @@ -37,6 +37,7 @@ import net.sf.cglib.proxy.MethodProxy; import org.apache.gravitino.catalog.hadoop.fs.kerberos.AuthenticationConfig; import org.apache.gravitino.catalog.hadoop.fs.kerberos.KerberosClient; import org.apache.gravitino.exceptions.GravitinoRuntimeException; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -157,7 +158,7 @@ public class HDFSFileSystemProxy implements MethodInterceptor { if (RuntimeException.class.isAssignableFrom(e.getClass())) { throw (RuntimeException) e; } - throw new RuntimeException("Failed to invoke method", e); + throw ExceptionMessages.wrap("Failed to invoke method", e); } }); } @@ -175,7 +176,7 @@ public class HDFSFileSystemProxy implements MethodInterceptor { this.kerberosRealm = client.getKerberosRealm(); return ugi; } catch (IOException e) { - throw new RuntimeException("Failed to login with Kerberos", e); + throw ExceptionMessages.wrap("Failed to login with Kerberos", e); } } } diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java index 544df5e195..45d6b7cfd8 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientFactory.java @@ -34,6 +34,7 @@ import org.apache.gravitino.exceptions.GravitinoRuntimeException; import org.apache.gravitino.hive.kerberos.AuthenticationConfig; import org.apache.gravitino.hive.kerberos.KerberosClient; import org.apache.gravitino.utils.ClassLoaderResourceCleanerUtils; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.PrincipalUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; @@ -110,7 +111,7 @@ public final class HiveClientFactory { "Failed to connect to Hive Metastore using cached Hive version {}", classLoader.getHiveVersion(), e); - throw new RuntimeException("Failed to connect to Hive Metastore", e); + throw ExceptionMessages.wrap("Failed to connect to Hive Metastore", e); } } @@ -247,7 +248,7 @@ public final class HiveClientFactory { kerberosClient.login(); } catch (Exception e) { - throw new RuntimeException("Failed to initialize kerberos client", e); + throw ExceptionMessages.wrap("Failed to initialize kerberos client", e); } } diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java index 550d730567..1f7818a9c8 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveClientImpl.java @@ -23,6 +23,7 @@ import java.util.Properties; import org.apache.gravitino.hive.HivePartition; import org.apache.gravitino.hive.HiveSchema; import org.apache.gravitino.hive.HiveTable; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.security.UserGroupInformation; /** @@ -184,7 +185,7 @@ public class HiveClientImpl implements HiveClient { try { shim.close(); } catch (Exception e) { - throw new RuntimeException("Failed to close HiveClient", e); + throw ExceptionMessages.wrap("Failed to close HiveClient", e); } } @@ -193,7 +194,7 @@ public class HiveClientImpl implements HiveClient { try { return UserGroupInformation.getCurrentUser(); } catch (Exception e) { - throw new RuntimeException("Failed to get current user", e); + throw ExceptionMessages.wrap("Failed to get current user", e); } } } diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java index 3eff0eaae4..cc262a6f51 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/HiveExceptionConverter.java @@ -33,6 +33,7 @@ import org.apache.gravitino.exceptions.NonEmptySchemaException; import org.apache.gravitino.exceptions.PartitionAlreadyExistsException; import org.apache.gravitino.exceptions.SchemaAlreadyExistsException; import org.apache.gravitino.exceptions.TableAlreadyExistsException; +import org.apache.gravitino.utils.ExceptionMessages; /** * Utility class to convert Hive exceptions to Gravitino exceptions. This class handles various @@ -190,7 +191,10 @@ public class HiveExceptionConverter { if (isConnectionKeyword(lowerMessage) || exceptionClassName.contains("TransportException")) { return new ConnectionFailedException( - cause, "Failed to connect to Hive Metastore: %s", target.name()); + cause, + "%s", + ExceptionMessages.withCause( + "Failed to connect to Hive Metastore: " + target.name(), cause)); } if (cause instanceof RuntimeException) { diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java index 6363148cdf..bf84b4a430 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/ProxyHiveClientImpl.java @@ -27,6 +27,7 @@ import java.lang.reflect.UndeclaredThrowableException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Properties; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.security.UserGroupInformation; /** @@ -66,7 +67,7 @@ public class ProxyHiveClientImpl implements InvocationHandler { new ProxyHiveClientImpl(client, ugi)); } catch (IOException | InterruptedException ex) { - throw new RuntimeException("Failed to create Kerberos Hive client", ex); + throw ExceptionMessages.wrap("Failed to create Kerberos Hive client", ex); } } diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java index 4cf18cb95f..0b7c88ef9b 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/client/Util.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Properties; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.slf4j.Logger; @@ -53,7 +54,7 @@ public class Util { properties.forEach((k, v) -> config.set(k.toString(), v.toString())); resolveMetastoreUriHosts(config); } catch (Exception e) { - throw new RuntimeException("Failed to create configuration", e); + throw ExceptionMessages.wrap("Failed to create configuration", e); } } diff --git a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java index 57ed6949e1..8be06ff787 100644 --- a/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java +++ b/catalogs/hive-metastore-common/src/main/java/org/apache/gravitino/hive/kerberos/KerberosClient.java @@ -36,6 +36,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.gravitino.hive.client.HiveClient; +import org.apache.gravitino.utils.ExceptionMessages; import org.apache.gravitino.utils.FileFetcher; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.thrift.DelegationTokenIdentifier; @@ -111,7 +112,7 @@ public class KerberosClient implements java.io.Closeable { return proxyUser; } catch (Exception e) { - throw new RuntimeException("Failed to create proxy user for Kerberos Hive client", e); + throw ExceptionMessages.wrap("Failed to create proxy user for Kerberos Hive client", e); } } diff --git a/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java b/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java new file mode 100644 index 0000000000..4818c3c6b1 --- /dev/null +++ b/common/src/main/java/org/apache/gravitino/utils/ExceptionMessages.java @@ -0,0 +1,155 @@ +/* + * 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.gravitino.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; + +/** + * Helpers for preserving underlying system error messages when wrapping exceptions. + * + * <p>Catalog and server code often adds operation context when rethrowing. Context is useful, but + * it must not replace the upstream message that operators need to act on. + */ +public final class ExceptionMessages { + + private ExceptionMessages() {} + + /** + * Returns a non-blank diagnostic message from {@code throwable} or its cause chain. + * + * <p>Transparent wrappers such as {@link ExecutionException} are skipped when selecting candidate + * messages. Among remaining frames, the shallowest non-blank message is preferred so nested + * {@link #wrap(String, Throwable)} / {@link #withCause(String, Throwable)} context is not + * discarded. When a deeper non-blank reason is not already contained in that message, it is + * appended. + * + * @param throwable the throwable to inspect, may be null + * @return a useful message, or null if none is available + */ + @Nullable + public static String usefulMessage(@Nullable Throwable throwable) { + if (throwable == null) { + return null; + } + + Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<>()); + String firstUseful = null; + String lastUseful = null; + Throwable current = throwable; + while (current != null) { + if (!visited.add(current)) { + break; + } + + Throwable cause = current.getCause(); + if (!isTransparentWrapper(current)) { + String message = current.getMessage(); + // new RuntimeException(executionException) copies cause.toString() as the detail + // message; ignore that synthetic text and keep walking into the real cause. + boolean syntheticTransparentMessage = + cause != null + && isTransparentWrapper(cause) + && message != null + && message.equals(cause.toString()); + if (StringUtils.isNotBlank(message) && !syntheticTransparentMessage) { + if (firstUseful == null) { + firstUseful = message; + } + lastUseful = message; + } + } + + if (cause == null || cause == current) { + break; + } + current = cause; + } + + if (firstUseful == null) { + return null; + } + if (lastUseful == null || firstUseful.equals(lastUseful) || firstUseful.contains(lastUseful)) { + return firstUseful; + } + return firstUseful + ": " + lastUseful; + } + + /** + * Combines operation context with the underlying cause message. + * + * <p>If {@code context} already contains the useful cause message, {@code context} is returned + * unchanged. If there is no useful cause message, {@code context} is returned as-is. + * + * @param context operation/object context such as {@code "Failed to alter topic X"} + * @param throwable the underlying failure + * @return a message that preserves both context and the upstream reason when available + */ + public static String withCause(String context, @Nullable Throwable throwable) { + String useful = usefulMessage(throwable); + if (StringUtils.isBlank(useful)) { + return context; + } + if (StringUtils.isBlank(context)) { + return useful; + } + if (context.contains(useful)) { + return context; + } + return context + ": " + useful; + } + + /** + * Wraps {@code throwable} in a {@link RuntimeException} whose message includes both {@code + * context} and the underlying cause message. + * + * @param context operation/object context + * @param throwable the underlying failure + * @return a runtime exception suitable for rethrowing from catalog operations + */ + public static RuntimeException wrap(String context, Throwable throwable) { + return new RuntimeException(withCause(context, throwable), throwable); + } + + /** + * Creates an {@link IllegalArgumentException} whose message includes both {@code context} and the + * underlying cause message. + * + * @param context operation/object context + * @param throwable the underlying failure + * @return an illegal-argument exception for client-caused failures + */ + public static IllegalArgumentException illegalArgument(String context, Throwable throwable) { + return new IllegalArgumentException(withCause(context, throwable), throwable); + } + + private static boolean isTransparentWrapper(Throwable throwable) { + return throwable instanceof ExecutionException + || throwable instanceof CompletionException + || throwable instanceof InvocationTargetException + || throwable instanceof UndeclaredThrowableException; + } +} diff --git a/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java b/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java new file mode 100644 index 0000000000..79d8b9ec9a --- /dev/null +++ b/common/src/test/java/org/apache/gravitino/utils/TestExceptionMessages.java @@ -0,0 +1,143 @@ +/* + * 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.gravitino.utils; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestExceptionMessages { + + @Test + public void testUsefulMessageUnwrapsTransparentWrappers() { + Throwable root = new IllegalArgumentException("root reason"); + Throwable mid = new ExecutionException(root); + Throwable top = new RuntimeException(mid); + + Assertions.assertEquals("root reason", ExceptionMessages.usefulMessage(top)); + } + + @Test + public void testUsefulMessageKeepsImmediateContextAndAppendsDeeperReason() { + Throwable root = new IllegalArgumentException("root reason"); + Throwable mid = new ExecutionException(root); + Throwable top = new RuntimeException("wrapper", mid); + + Assertions.assertEquals("wrapper: root reason", ExceptionMessages.usefulMessage(top)); + } + + @Test + public void testUsefulMessageIgnoresBlankMessages() { + Throwable blankCause = new IOException(" "); + Throwable cause = new IOException("HMS connection refused", blankCause); + + Assertions.assertEquals( + "Failed to load table: HMS connection refused", + ExceptionMessages.withCause("Failed to load table", cause)); + Assertions.assertEquals("HMS connection refused", ExceptionMessages.usefulMessage(cause)); + } + + @Test + public void testUsefulMessageBlankOnlyChainReturnsNull() { + Throwable blank = new IOException("\n\t "); + Assertions.assertNull(ExceptionMessages.usefulMessage(blank)); + Assertions.assertEquals( + "Failed to load table", ExceptionMessages.withCause("Failed to load table", blank)); + } + + @Test + public void testUsefulMessageStopsOnTwoNodeCycle() { + RuntimeException a = new RuntimeException("a"); + RuntimeException b = new RuntimeException("b"); + a.initCause(b); + b.initCause(a); + + Assertions.assertEquals("a: b", ExceptionMessages.usefulMessage(a)); + } + + @Test + public void testUsefulMessageStopsOnThreeNodeCycle() { + RuntimeException a = new RuntimeException("a"); + RuntimeException b = new RuntimeException("b"); + RuntimeException c = new RuntimeException("c"); + a.initCause(b); + b.initCause(c); + c.initCause(a); + + Assertions.assertEquals("a: c", ExceptionMessages.usefulMessage(a)); + } + + @Test + public void testNestedWrapPreservesIntermediatePropertyContext() { + Throwable root = new IOException("write failed"); + Throwable inner = ExceptionMessages.wrap("Failed to write property: fs.s3a.endpoint", root); + RuntimeException outer = ExceptionMessages.wrap("Failed to create configuration", inner); + + Assertions.assertTrue( + outer.getMessage().contains("fs.s3a.endpoint"), + "nested wrap must keep intermediate property context, got: " + outer.getMessage()); + Assertions.assertEquals( + "Failed to create configuration: Failed to write property: fs.s3a.endpoint: write failed", + outer.getMessage()); + } + + @Test + public void testWithCauseAppendsUpstreamMessage() { + Throwable cause = + new IllegalArgumentException( + "Invalid value nonsense for configuration cleanup.policy: String must be one of:" + + " compact, delete"); + + String combined = + ExceptionMessages.withCause("Failed to alter topic properties for topic prop_probe", cause); + + Assertions.assertEquals( + "Failed to alter topic properties for topic prop_probe: Invalid value nonsense for" + + " configuration cleanup.policy: String must be one of: compact, delete", + combined); + } + + @Test + public void testWithCauseDoesNotDuplicateMessage() { + String context = "Failed to alter topic: bad value"; + Throwable cause = new IllegalArgumentException("bad value"); + + Assertions.assertEquals(context, ExceptionMessages.withCause(context, cause)); + } + + @Test + public void testWrapPreservesCauseAndMessage() { + Throwable cause = new IllegalStateException("glue denied"); + RuntimeException wrapped = ExceptionMessages.wrap("Glue error: schema drop_me", cause); + + Assertions.assertEquals("Glue error: schema drop_me: glue denied", wrapped.getMessage()); + Assertions.assertSame(cause, wrapped.getCause()); + } + + @Test + public void testIllegalArgumentPreservesCauseAndMessage() { + Throwable cause = new IllegalArgumentException("not allowed"); + IllegalArgumentException wrapped = + ExceptionMessages.illegalArgument("Invalid properties for topic t1", cause); + + Assertions.assertEquals("Invalid properties for topic t1: not allowed", wrapped.getMessage()); + Assertions.assertSame(cause, wrapped.getCause()); + } +}
