Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions core/src/main/java/org/apache/iceberg/InternalData.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,31 @@ static void register(
READ_BUILDERS.put(format, readBuilder);
}

@SuppressWarnings("CatchBlockLogException")
private static void registerSupportedFormats() {
InternalData.register(
FileFormat.AVRO,
outputFile -> Avro.write(outputFile).createWriterFunc(InternalWriter::create),
inputFile -> Avro.read(inputFile).createResolvingReader(InternalReader::create));

try {
DynMethods.StaticMethod registerParquet =
DynMethods.builder("register")
.impl("org.apache.iceberg.InternalParquet")
.buildStaticChecked();

registerParquet.invoke();
register("org.apache.iceberg.InternalParquet");
}

} catch (NoSuchMethodException e) {
// failing to load Parquet is normal and does not require a stack trace
LOG.info("Unable to register Parquet for metadata files: {}", e.getMessage());
/**
* Invokes the static {@code register} method of the given class, tolerating the failure modes
* that occur when an optional module (like {@code iceberg-parquet}) is not on the classpath.
*
* <p>Besides {@link NoSuchMethodException}, invoking {@code register} can fail with {@link
* NoClassDefFoundError} when its body references a missing transitive dependency, or {@link
* ExceptionInInitializerError} when it triggers a failing static initializer.
*/
@SuppressWarnings("CatchBlockLogException")
private static void register(String classToRegister) {
try {
DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
} catch (NoSuchMethodException | NoClassDefFoundError | ExceptionInInitializerError e) {
// failing to load an optional format is normal and does not require a stack trace
Throwable cause = e.getCause() != null ? e.getCause() : e;
LOG.info("Cannot register {} for metadata files: {}", classToRegister, cause.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,19 +199,33 @@ private static <D, S> FormatModel<D, S> modelFor(FileFormat format, Class<? exte
return model;
}

@SuppressWarnings("CatchBlockLogException")
private static void registerSupportedFormats() {
// Uses dynamic methods to call the `register` for the listed classes
for (String classToRegister : CLASSES_TO_REGISTER) {
try {
DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
} catch (NoSuchMethodException e) {
// failing to register a factory is normal and does not require a stack trace
LOG.info(
"Unable to call register for ({}). Check for missing jars on the classpath: {}",
classToRegister,
e.getMessage());
}
register(classToRegister);
}
}

/**
* Invokes the static {@code register} method of the given class, tolerating the failure modes
* that occur when an optional module (like {@code iceberg-parquet} or {@code iceberg-orc}) is not
* on the classpath.
*
* <p>Besides {@link NoSuchMethodException}, invoking {@code register} can fail with {@link
* NoClassDefFoundError} when its body references a missing transitive dependency, or {@link
* ExceptionInInitializerError} when it triggers a failing static initializer.
*/
@SuppressWarnings("CatchBlockLogException")
private static void register(String classToRegister) {
try {
DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
} catch (NoSuchMethodException | NoClassDefFoundError | ExceptionInInitializerError e) {
// failing to register a factory is normal and does not require a stack trace
Throwable cause = e.getCause() != null ? e.getCause() : e;
LOG.info(
"Unable to call register for ({}). Check for missing jars on the classpath: {}",
classToRegister,
cause.toString());
}
}
}
36 changes: 36 additions & 0 deletions core/src/test/java/org/apache/iceberg/TestInternalData.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
package org.apache.iceberg;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;

import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
Expand All @@ -32,6 +34,7 @@
import org.apache.iceberg.io.OutputFile;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.types.Types;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
Expand Down Expand Up @@ -155,4 +158,37 @@ public void testCustomTypeForNestedField() throws IOException {
}
}
}

@Test
public void registerToleratesMissingClass() {
assertThatNoException().isThrownBy(() -> register("org.apache.iceberg.DoesNotExist"));
}

@Test
public void registerToleratesNoClassDefFoundErrorOnInvoke() {
assertThatNoException().isThrownBy(() -> register(ThrowsOnInvoke.class.getName()));
}

@Test
public void registerToleratesExceptionInInitializerErrorOnInvoke() {
assertThatNoException().isThrownBy(() -> register(ThrowsInitErrorOnInvoke.class.getName()));
}

private static void register(String className) throws ReflectiveOperationException {
Method register = InternalData.class.getDeclaredMethod("register", String.class);
register.setAccessible(true);
register.invoke(null, className);
}

public static class ThrowsOnInvoke {
public static void register() {
throw new NoClassDefFoundError("some/missing/TransitiveDependency");
}
}

public static class ThrowsInitErrorOnInvoke {
public static void register() {
throw new ExceptionInInitializerError(new RuntimeException("lazy init failed"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
*/
package org.apache.iceberg.formats;

import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

import java.lang.reflect.Method;
import org.apache.iceberg.FileFormat;
import org.apache.iceberg.encryption.EncryptedOutputFile;
import org.apache.iceberg.io.InputFile;
Expand Down Expand Up @@ -86,6 +88,42 @@ void testFailingReRegistrations() {
.hasMessageContaining("Cannot register class");
}

@Test
void registerToleratesMissingClass() {
assertThatNoException().isThrownBy(() -> register("org.apache.iceberg.formats.DoesNotExist"));
assertThat(FormatModelRegistry.models()).isEmpty();
}

@Test
void registerToleratesNoClassDefFoundErrorOnInvoke() {
assertThatNoException().isThrownBy(() -> register(ThrowsOnInvoke.class.getName()));
assertThat(FormatModelRegistry.models()).isEmpty();
}

@Test
void registerToleratesExceptionInInitializerErrorOnInvoke() {
assertThatNoException().isThrownBy(() -> register(ThrowsInitErrorOnInvoke.class.getName()));
assertThat(FormatModelRegistry.models()).isEmpty();
}

private static void register(String className) throws ReflectiveOperationException {
Method register = FormatModelRegistry.class.getDeclaredMethod("register", String.class);
register.setAccessible(true);
register.invoke(null, className);
}

public static class ThrowsOnInvoke {
public static void register() {
throw new NoClassDefFoundError("some/missing/TransitiveDependency");
}
}

public static class ThrowsInitErrorOnInvoke {
public static void register() {
throw new ExceptionInInitializerError(new RuntimeException("lazy init failed"));
}
}

private static class DummyParquetFormatModel implements FormatModel<Object, Object> {
private final Class<?> type;
private final Class<?> schemaType;
Expand Down
Loading