-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Filter service loading candidates using new
@CheckAvailability
anno…
…tation
- Loading branch information
1 parent
e7a693a
commit d9785e0
Showing
4 changed files
with
293 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
48 changes: 48 additions & 0 deletions
48
src/main/java/org/cryptomator/integrations/common/CheckAvailability.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package org.cryptomator.integrations.common; | ||
|
||
import org.jetbrains.annotations.ApiStatus; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Inherited; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Identifies 0..n public methods to check preconditions for the integration to work. These are the rules: | ||
* | ||
* <ul> | ||
* <li>Both the type and the method(s) must be annotated with {@code @CheckAvailability}</li> | ||
* <li>Only public no-arg boolean methods are considered</li> | ||
* <li>Methods <em>may</em> be {@code static}, in which case they get invoked before instantiating the service</li> | ||
* <li>Should the method throw an exception, it has the same effect as returning {@code false}</li> | ||
* <li>No specific execution order is guaranteed in case of multiple annotated methods</li> | ||
* <li>Annotations must be present on classes or ancestor classes, not on interfaces</li> | ||
* </ul> | ||
* | ||
* Example: | ||
* <pre> | ||
* {@code | ||
* @CheckAvailability | ||
* public class Foo { | ||
* @CheckAvailability | ||
* public static boolean isSupported() { | ||
* return "enabled".equals(System.getProperty("plugin.status")); | ||
* } | ||
* } | ||
* } | ||
* </pre> | ||
* <p> | ||
* Annotations are discovered at runtime using reflection, so make sure to make relevant classes accessible to this | ||
* module ({@code opens X to org.cryptomator.integrations.api}). | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
@Documented | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ElementType.TYPE, ElementType.METHOD}) | ||
@Inherited | ||
@ApiStatus.Experimental | ||
public @interface CheckAvailability { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
195 changes: 195 additions & 0 deletions
195
src/test/java/org/cryptomator/integrations/common/IntegrationsLoaderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package org.cryptomator.integrations.common; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class IntegrationsLoaderTest { | ||
|
||
@Nested | ||
@DisplayName("@CheckAvailability on static methods") | ||
public class StaticAvailabilityChecks { | ||
|
||
@CheckAvailability | ||
private static class StaticTrue { | ||
@CheckAvailability | ||
public static boolean test() { | ||
return true; | ||
} | ||
} | ||
|
||
@CheckAvailability | ||
private static class StaticFalse { | ||
@CheckAvailability | ||
public static boolean test() { | ||
return false; | ||
} | ||
} | ||
|
||
@Test | ||
@DisplayName("no @CheckAvailability will always pass") | ||
public void testPassesAvailabilityCheck0() { | ||
// @formatter:off | ||
class C1 {} | ||
@CheckAvailability class C2 {} | ||
class C3 { | ||
@CheckAvailability public static boolean test() { return false; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class)); | ||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(C2.class)); | ||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(C3.class)); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on non-conforming methods will be skipped") | ||
public void testPassesAvailabilityCheck1() { | ||
// @formatter:off | ||
@CheckAvailability class C1 { | ||
@CheckAvailability private static boolean test1() { return false; } | ||
@CheckAvailability public static boolean test2(String foo) { return false; } | ||
@CheckAvailability public static String test3() { return "false"; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class)); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on static method") | ||
public void testPassesAvailabilityCheck2() { | ||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(StaticTrue.class)); | ||
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(StaticFalse.class)); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on inherited static method") | ||
public void testPassesAvailabilityCheck3() { | ||
// @formatter:off | ||
class C1 extends StaticTrue {} | ||
class C2 extends StaticFalse {} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class)); | ||
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C2.class)); | ||
} | ||
|
||
@Test | ||
@DisplayName("multiple @CheckAvailability methods") | ||
public void testPassesAvailabilityCheck4() { | ||
// @formatter:off | ||
class C1 extends StaticTrue { | ||
@CheckAvailability public static boolean test1() { return false; } | ||
} | ||
class C2 extends StaticFalse { | ||
@CheckAvailability public static boolean test1() { return true; } | ||
} | ||
@CheckAvailability class C3 { | ||
@CheckAvailability public static boolean test1() { return true; } | ||
@CheckAvailability public static boolean test2() { return false; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C1.class)); | ||
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C2.class)); | ||
Assertions.assertFalse(IntegrationsLoader.passesStaticAvailabilityCheck(C3.class)); | ||
} | ||
|
||
|
||
} | ||
|
||
@Nested | ||
@DisplayName("@CheckAvailability on instance methods") | ||
public class InstanceAvailabilityChecks { | ||
|
||
@CheckAvailability | ||
private static class InstanceTrue { | ||
@CheckAvailability | ||
public boolean test() { | ||
return true; | ||
} | ||
} | ||
|
||
@CheckAvailability | ||
private static class InstanceFalse { | ||
@CheckAvailability | ||
public boolean test() { | ||
return false; | ||
} | ||
} | ||
|
||
@Test | ||
@DisplayName("no @CheckAvailability will always pass") | ||
public void testPassesAvailabilityCheck0() { | ||
// @formatter:off | ||
class C1 {} | ||
@CheckAvailability class C2 {} | ||
class C3 { | ||
@CheckAvailability public boolean test() { return false; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(new C1())); | ||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(new C2())); | ||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(new C3())); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on non-conforming instance methods will be skipped") | ||
public void testPassesAvailabilityCheck1() { | ||
// @formatter:off | ||
@CheckAvailability class C1 { | ||
@CheckAvailability private boolean test1() { return false; } | ||
@CheckAvailability public boolean test2(String foo) { return false; } | ||
@CheckAvailability public String test3() { return "false"; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(C1.class)); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on instance method") | ||
public void testPassesAvailabilityCheck2() { | ||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(new InstanceTrue())); | ||
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new InstanceFalse())); | ||
} | ||
|
||
@Test | ||
@DisplayName("@CheckAvailability on inherited instance method") | ||
public void testPassesAvailabilityCheck3() { | ||
// @formatter:off | ||
class C1 extends InstanceTrue {} | ||
class C2 extends InstanceFalse {} | ||
// @formatter:on | ||
|
||
Assertions.assertTrue(IntegrationsLoader.passesInstanceAvailabilityCheck(new C1())); | ||
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C2())); | ||
} | ||
|
||
@Test | ||
@DisplayName("multiple @CheckAvailability methods") | ||
public void testPassesAvailabilityCheck4() { | ||
// @formatter:off | ||
class C1 extends InstanceTrue { | ||
@CheckAvailability public boolean test1() { return false; } | ||
} | ||
class C2 extends InstanceFalse { | ||
@CheckAvailability public boolean test1() { return true; } | ||
} | ||
@CheckAvailability class C3 { | ||
@CheckAvailability public boolean test1() { return true; } | ||
@CheckAvailability public boolean test2() { return false; } | ||
} | ||
// @formatter:on | ||
|
||
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C1())); | ||
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C2())); | ||
Assertions.assertFalse(IntegrationsLoader.passesInstanceAvailabilityCheck(new C3())); | ||
} | ||
|
||
} | ||
|
||
} |