diff --git a/src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java b/src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java index 64a3977..65d8785 100644 --- a/src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java +++ b/src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Optional; +import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.stream.Stream; @@ -47,7 +48,7 @@ public static Stream loadAll(Class clazz) { .filter(IntegrationsLoader::isSupportedOperatingSystem) .filter(IntegrationsLoader::passesStaticAvailabilityCheck) .sorted(Comparator.comparingInt(IntegrationsLoader::getPriority).reversed()) - .map(ServiceLoader.Provider::get) + .flatMap(IntegrationsLoader::instantiateServiceProvider) .filter(IntegrationsLoader::passesInstanceAvailabilityCheck) .peek(impl -> logServiceIsAvailable(clazz, impl.getClass())); } @@ -68,18 +69,30 @@ private static boolean isSupportedOperatingSystem(ServiceLoader.Provider prov return annotations.length == 0 || Arrays.stream(annotations).anyMatch(OperatingSystem.Value::isCurrent); } + private static Stream instantiateServiceProvider(ServiceLoader.Provider provider) { + try { + return Stream.of(provider.get()); + } catch (ServiceConfigurationError err) { + //ServiceLoader.Provider::get throws this error if (from javadoc) + // * the public static "provider()" method of a provider factory returns null + // * the service provider cannot be instantiated due to an error/throw + LOG.warn("Unable to load service provider {}.", provider.type().getName(), err); + return Stream.empty(); + } + } + private static boolean passesStaticAvailabilityCheck(ServiceLoader.Provider provider) { return passesStaticAvailabilityCheck(provider.type()); } @VisibleForTesting static boolean passesStaticAvailabilityCheck(Class type) { - return passesAvailabilityCheck(type, null); + return silentlyPassesAvailabilityCheck(type, null); } @VisibleForTesting static boolean passesInstanceAvailabilityCheck(Object instance) { - return passesAvailabilityCheck(instance.getClass(), instance); + return silentlyPassesAvailabilityCheck(instance.getClass(), instance); } private static void logServiceIsAvailable(Class apiType, Class implType) { @@ -88,6 +101,15 @@ private static void logServiceIsAvailable(Class apiType, Class implType) { } } + private static boolean silentlyPassesAvailabilityCheck(Class type, @Nullable T instance) { + try { + return passesAvailabilityCheck(type, instance); + } catch (ExceptionInInitializerError | NoClassDefFoundError | RuntimeException e) { + LOG.warn("Unable to load service provider {}.", type.getName(), e); + return false; + } + } + private static boolean passesAvailabilityCheck(Class type, @Nullable T instance) { if (!type.isAnnotationPresent(CheckAvailability.class)) { return true; // if type is not annotated, skip tests diff --git a/src/test/java/org/cryptomator/integrations/common/InitExceptionTestClass.java b/src/test/java/org/cryptomator/integrations/common/InitExceptionTestClass.java new file mode 100644 index 0000000..800e370 --- /dev/null +++ b/src/test/java/org/cryptomator/integrations/common/InitExceptionTestClass.java @@ -0,0 +1,25 @@ +package org.cryptomator.integrations.common; + +@CheckAvailability +public class InitExceptionTestClass { + + private static final String TEST; + + static { + TEST = throwSomething(); + } + + public InitExceptionTestClass() { + + } + + static String throwSomething() { + throw new RuntimeException("STATIC FAIL"); + } + + @CheckAvailability + public static boolean test() { + return true; + } + +} diff --git a/src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java b/src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java index 5c7c41a..1fbc614 100644 --- a/src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java +++ b/src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java @@ -97,6 +97,18 @@ class C2 extends StaticFalse { Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C3.class)); } + @Test + @DisplayName("throwing @CheckAvailability methods are treated as false") + public void testPassesAvailabilityCheckThrowing() { + + @CheckAvailability class C1 { + @CheckAvailability public static boolean test() { throw new RuntimeException("FAIL"); } + } + + Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class)); + Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(InitExceptionTestClass.class)); + Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(InitExceptionTestClass.class)); //NoClassDefFoundError due to repated call + } } @@ -190,6 +202,26 @@ class C2 extends InstanceFalse { Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C3())); } + + @Test + @DisplayName("throwing @CheckAvailability methods are treated as false") + public void testPassesAvailabilityCheckThrowing() { + + @CheckAvailability + class C1 { + @CheckAvailability public boolean test1() { throw new RuntimeException("FAIL"); } + } + + @CheckAvailability + class C2 { + @CheckAvailability public boolean test1() { return true; } + @CheckAvailability public boolean test2() { throw new RuntimeException("FAIL"); } + } + + Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C1())); + Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C2())); + } + } } \ No newline at end of file