Skip to content

Commit 1005a81

Browse files
authored
Merge pull request #27 from cryptomator/feature/26-robust-service-loading
Feature: Robust service provider loading
2 parents 261b915 + bbd8616 commit 1005a81

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

src/main/java/org/cryptomator/integrations/common/IntegrationsLoader.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Arrays;
1111
import java.util.Comparator;
1212
import java.util.Optional;
13+
import java.util.ServiceConfigurationError;
1314
import java.util.ServiceLoader;
1415
import java.util.stream.Stream;
1516

@@ -47,7 +48,7 @@ public static <T> Stream<T> loadAll(Class<T> clazz) {
4748
.filter(IntegrationsLoader::isSupportedOperatingSystem)
4849
.filter(IntegrationsLoader::passesStaticAvailabilityCheck)
4950
.sorted(Comparator.comparingInt(IntegrationsLoader::getPriority).reversed())
50-
.map(ServiceLoader.Provider::get)
51+
.flatMap(IntegrationsLoader::instantiateServiceProvider)
5152
.filter(IntegrationsLoader::passesInstanceAvailabilityCheck)
5253
.peek(impl -> logServiceIsAvailable(clazz, impl.getClass()));
5354
}
@@ -68,18 +69,30 @@ private static boolean isSupportedOperatingSystem(ServiceLoader.Provider<?> prov
6869
return annotations.length == 0 || Arrays.stream(annotations).anyMatch(OperatingSystem.Value::isCurrent);
6970
}
7071

72+
private static <T> Stream<T> instantiateServiceProvider(ServiceLoader.Provider<T> provider) {
73+
try {
74+
return Stream.of(provider.get());
75+
} catch (ServiceConfigurationError err) {
76+
//ServiceLoader.Provider::get throws this error if (from javadoc)
77+
// * the public static "provider()" method of a provider factory returns null
78+
// * the service provider cannot be instantiated due to an error/throw
79+
LOG.warn("Unable to load service provider {}.", provider.type().getName(), err);
80+
return Stream.empty();
81+
}
82+
}
83+
7184
private static boolean passesStaticAvailabilityCheck(ServiceLoader.Provider<?> provider) {
7285
return passesStaticAvailabilityCheck(provider.type());
7386
}
7487

7588
@VisibleForTesting
7689
static boolean passesStaticAvailabilityCheck(Class<?> type) {
77-
return passesAvailabilityCheck(type, null);
90+
return silentlyPassesAvailabilityCheck(type, null);
7891
}
7992

8093
@VisibleForTesting
8194
static boolean passesInstanceAvailabilityCheck(Object instance) {
82-
return passesAvailabilityCheck(instance.getClass(), instance);
95+
return silentlyPassesAvailabilityCheck(instance.getClass(), instance);
8396
}
8497

8598
private static void logServiceIsAvailable(Class<?> apiType, Class<?> implType) {
@@ -88,6 +101,15 @@ private static void logServiceIsAvailable(Class<?> apiType, Class<?> implType) {
88101
}
89102
}
90103

104+
private static <T> boolean silentlyPassesAvailabilityCheck(Class<? extends T> type, @Nullable T instance) {
105+
try {
106+
return passesAvailabilityCheck(type, instance);
107+
} catch (ExceptionInInitializerError | NoClassDefFoundError | RuntimeException e) {
108+
LOG.warn("Unable to load service provider {}.", type.getName(), e);
109+
return false;
110+
}
111+
}
112+
91113
private static <T> boolean passesAvailabilityCheck(Class<? extends T> type, @Nullable T instance) {
92114
if (!type.isAnnotationPresent(CheckAvailability.class)) {
93115
return true; // if type is not annotated, skip tests
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.cryptomator.integrations.common;
2+
3+
@CheckAvailability
4+
public class InitExceptionTestClass {
5+
6+
private static final String TEST;
7+
8+
static {
9+
TEST = throwSomething();
10+
}
11+
12+
public InitExceptionTestClass() {
13+
14+
}
15+
16+
static String throwSomething() {
17+
throw new RuntimeException("STATIC FAIL");
18+
}
19+
20+
@CheckAvailability
21+
public static boolean test() {
22+
return true;
23+
}
24+
25+
}

src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ class C2 extends StaticFalse {
9797
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C3.class));
9898
}
9999

100+
@Test
101+
@DisplayName("throwing @CheckAvailability methods are treated as false")
102+
public void testPassesAvailabilityCheckThrowing() {
103+
104+
@CheckAvailability class C1 {
105+
@CheckAvailability public static boolean test() { throw new RuntimeException("FAIL"); }
106+
}
107+
108+
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class));
109+
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(InitExceptionTestClass.class));
110+
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(InitExceptionTestClass.class)); //NoClassDefFoundError due to repated call
111+
}
100112

101113
}
102114

@@ -190,6 +202,26 @@ class C2 extends InstanceFalse {
190202
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C3()));
191203
}
192204

205+
206+
@Test
207+
@DisplayName("throwing @CheckAvailability methods are treated as false")
208+
public void testPassesAvailabilityCheckThrowing() {
209+
210+
@CheckAvailability
211+
class C1 {
212+
@CheckAvailability public boolean test1() { throw new RuntimeException("FAIL"); }
213+
}
214+
215+
@CheckAvailability
216+
class C2 {
217+
@CheckAvailability public boolean test1() { return true; }
218+
@CheckAvailability public boolean test2() { throw new RuntimeException("FAIL"); }
219+
}
220+
221+
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C1()));
222+
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C2()));
223+
}
224+
193225
}
194226

195227
}

0 commit comments

Comments
 (0)