Skip to content

Commit

Permalink
Merge pull request #144 from aguibert/appenv-loading
Browse files Browse the repository at this point in the history
Explicitly trigger static init of SharedContainerConfig early in extension lifecycle
  • Loading branch information
aguibert committed Feb 6, 2020
2 parents af0018a + fe3e428 commit f22dcfd
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 70 deletions.
133 changes: 73 additions & 60 deletions core/src/main/java/org/microshed/testing/ApplicationEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,78 @@
*/
public interface ApplicationEnvironment {

public static class Resolver {
private static ApplicationEnvironment loaded = null;

private Resolver() {
// static singleton
}

/**
* @return The selected {@link ApplicationEnvironment}. The selection is made using the following criteria:
* <ol>
* <li>If the {@link #ENV_CLASS} system property or environment variable is set, the class is used</li>
* <li>The {@link ServiceLoader} is used to load all {@link ApplicationEnvironment} instances, which are filtered
* based on {@link #isAvailable()} and then sorted based on {@link #getPriority()} where higher numbers are chosen
* first.</li>
* </ol>
*/
public static ApplicationEnvironment load() {
if (loaded != null)
return loaded;

// First check explicilty configured environment via system property or env var
String strategy = System.getProperty(ENV_CLASS);
if (strategy == null || strategy.isEmpty())
strategy = System.getenv(ENV_CLASS);
if (strategy != null && !strategy.isEmpty()) {
Class<?> found;
try {
found = Class.forName(strategy);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to load the selected ApplicationEnvironment class: " + strategy, e);
}
if (!ApplicationEnvironment.class.isAssignableFrom(found)) {
throw new IllegalStateException("ApplicationEnvironment class " + strategy +
" was found, but it does not implement the required interface " + ApplicationEnvironment.class);
} else {
try {
loaded = (ApplicationEnvironment) found.newInstance();
return loaded;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Unable to initialize " + found, e);
}
}
}

Logger LOG = LoggerFactory.getLogger(ApplicationEnvironment.class);

// If nothing explicitly defined in sysprops or env, check ServiceLoader
Set<ApplicationEnvironment> envs = new HashSet<>();
ServiceLoader.load(ApplicationEnvironment.class).forEach(envs::add);
Optional<ApplicationEnvironment> selectedEnv = envs.stream()
.map(env -> {
if (LOG.isDebugEnabled())
LOG.debug("Found ApplicationEnvironment " + env.getClass() + " with priority=" + env.getPriority() + ", available=" + env.isAvailable());
return env;
})
.filter(env -> env.isAvailable())
.sorted((c1, c2) -> c1.getClass().getCanonicalName().compareTo(c2.getClass().getCanonicalName()))
.sorted((c1, c2) -> Integer.compare(c2.getPriority(), c1.getPriority()))
.findFirst();
loaded = selectedEnv.orElseThrow(() -> new IllegalStateException("No available " + ApplicationEnvironment.class.getSimpleName() + " was discovered."));
return loaded;
}

/**
* @param clazz The {@link ApplicationEnvironment} class to check is active
* @return True if the provided {@link ApplicationEnvironment} is currently active, false otherwise
*/
public static boolean isSelected(Class<? extends ApplicationEnvironment> clazz) {
return load().getClass().getCanonicalName().equals(clazz.getCanonicalName());
}
}

/**
* The default priority returned by an implementation of {@link ApplicationEnvironment#isAvailable}
* In general, built-in ApplicationEnvironment implementations have a priority less than the default
Expand All @@ -47,69 +119,10 @@ public interface ApplicationEnvironment {
/**
* The name of the system property or environment variable that indicates a specific {@link ApplicationEnvironment}
* to use. If this property is set, it will be used regardless of the priority or availability. If this property is
* NOT set, the normal resolution rules will be applied as defined in {@link #load()}
* NOT set, the normal resolution rules will be applied as defined in {@link ApplicationEnvironment.Resolver#load()}
*/
public static final String ENV_CLASS = "MICROSHED_TEST_ENV_CLASS";

/**
* @return The selected {@link ApplicationEnvironment}. The selection is made using the following criteria:
* <ol>
* <li>If the {@link #ENV_CLASS} system property or environment variable is set, the class is used</li>
* <li>The {@link ServiceLoader} is used to load all {@link ApplicationEnvironment} instances, which are filtered
* based on {@link #isAvailable()} and then sorted based on {@link #getPriority()} where higher numbers are chosen
* first.</li>
* </ol>
*/
public static ApplicationEnvironment load() {
// First check explicilty configured environment via system property or env var
String strategy = System.getProperty(ENV_CLASS);
if (strategy == null || strategy.isEmpty())
strategy = System.getenv(ENV_CLASS);
if (strategy != null && !strategy.isEmpty()) {
Class<?> found;
try {
found = Class.forName(strategy);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to load the selected ApplicationEnvironment class: " + strategy, e);
}
if (!ApplicationEnvironment.class.isAssignableFrom(found)) {
throw new IllegalStateException("ApplicationEnvironment class " + strategy +
" was found, but it does not implement the required interface " + ApplicationEnvironment.class);
} else {
try {
return (ApplicationEnvironment) found.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Unable to initialize " + found, e);
}
}
}

Logger LOG = LoggerFactory.getLogger(ApplicationEnvironment.class);

// If nothing explicitly defined in sysprops or env, check ServiceLoader
Set<ApplicationEnvironment> envs = new HashSet<>();
ServiceLoader.load(ApplicationEnvironment.class).forEach(envs::add);
Optional<ApplicationEnvironment> selectedEnv = envs.stream()
.map(env -> {
if (LOG.isDebugEnabled())
LOG.debug("Found ApplicationEnvironment " + env.getClass() + " with priority=" + env.getPriority() + ", available=" + env.isAvailable());
return env;
})
.filter(env -> env.isAvailable())
.sorted((c1, c2) -> c1.getClass().getCanonicalName().compareTo(c2.getClass().getCanonicalName()))
.sorted((c1, c2) -> Integer.compare(c2.getPriority(), c1.getPriority()))
.findFirst();
return selectedEnv.orElseThrow(() -> new IllegalStateException("No available " + ApplicationEnvironment.class.getSimpleName() + " was discovered."));
}

/**
* @param clazz The {@link ApplicationEnvironment} class to check is active
* @return True if the provided {@link ApplicationEnvironment} is currently active, false otherwise
*/
public static boolean isSelected(Class<? extends ApplicationEnvironment> clazz) {
return load().getClass().equals(clazz);
}

/**
* @return true if the ApplicationEnvironment is currently available
* false otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public RestClientBuilder withProviders(Class<?>... providers) {
public <T> T build(Class<T> clazz) {
// Apply default values if unspecified
if (appContextRoot == null)
appContextRoot = ApplicationEnvironment.load().getApplicationURL();
appContextRoot = ApplicationEnvironment.Resolver.load().getApplicationURL();
if (jaxrsPath == null)
jaxrsPath = locateApplicationPath(clazz);
if (providers == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.support.AnnotationSupport;
import org.microshed.testing.ApplicationEnvironment;
import org.microshed.testing.SharedContainerConfig;
import org.microshed.testing.jaxrs.RESTClient;
import org.microshed.testing.jaxrs.RestClientBuilder;
import org.microshed.testing.jwt.JwtBuilder;
Expand All @@ -50,7 +51,13 @@ class MicroShedTestExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
Class<?> testClass = context.getRequiredTestClass();
ApplicationEnvironment config = ApplicationEnvironment.load();

// Explicitly trigger static initialization of any SharedContainerConfig before we do further processing
if (testClass.isAnnotationPresent(SharedContainerConfig.class)) {
Class.forName(testClass.getAnnotation(SharedContainerConfig.class).value().getName());
}

ApplicationEnvironment config = ApplicationEnvironment.Resolver.load();
LOG.info("Using ApplicationEnvironment class: " + config.getClass().getCanonicalName());
config.applyConfiguration(testClass);
config.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public class TestcontainersConfigurationIT {

@Test
public void testCorrectEnvironment() {
assertEquals(TestcontainersConfiguration.class, ApplicationEnvironment.load().getClass());
assertTrue(ApplicationEnvironment.isSelected(TestcontainersConfiguration.class),
assertEquals(TestcontainersConfiguration.class, ApplicationEnvironment.Resolver.load().getClass());
assertTrue(ApplicationEnvironment.Resolver.isSelected(TestcontainersConfiguration.class),
"Expected TestcontainersConfiguration to be selected but it was not");
}

Expand All @@ -66,7 +66,7 @@ public void testExposedPort() {

@Test
public void testApplicationURL() {
String appUrl = ApplicationEnvironment.load().getApplicationURL();
String appUrl = ApplicationEnvironment.Resolver.load().getApplicationURL();
assertNotNull(appUrl);
assertEquals(appUrl, app.getApplicationURL());
assertTrue(appUrl.startsWith("http://"), "Application URL did not start with 'http://' " + appUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private static Future<String> resolveImage(Optional<Path> dockerfile) {
}

private static boolean isHollow() {
ApplicationEnvironment current = ApplicationEnvironment.load();
ApplicationEnvironment current = ApplicationEnvironment.Resolver.load();
return !(current instanceof TestcontainersConfiguration) ||
current instanceof HollowTestcontainersConfiguration;
}
Expand Down Expand Up @@ -429,7 +429,7 @@ private String readMpRestClientConfigKey(Class<?> restClientClass) {
public ApplicationContainer withMpRestClient(String restClientClass, String hostUrl) {
// If we will be running in Docker, sanitize environment variable name using Environment Variables Mapping Rules defined in MP Config:
// https://github.com/eclipse/microprofile-config/blob/master/spec/src/main/asciidoc/configsources.asciidoc#environment-variables-mapping-rules
if (ApplicationEnvironment.isSelected(TestcontainersConfiguration.class)) {
if (ApplicationEnvironment.Resolver.isSelected(TestcontainersConfiguration.class)) {
restClientClass = restClientClass.replaceAll("[^a-zA-Z0-9_]", "_") + "_mp_rest_url";
} else {
restClientClass += "/mp-rest/url";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public class HollowTestcontainersConfigurationTest {

@Test
public void testCorrectEnvironment() {
assertEquals(HollowTestcontainersConfiguration.class, ApplicationEnvironment.load().getClass());
assertTrue(ApplicationEnvironment.isSelected(HollowTestcontainersConfiguration.class),
assertEquals(HollowTestcontainersConfiguration.class, ApplicationEnvironment.Resolver.load().getClass());
assertTrue(ApplicationEnvironment.Resolver.isSelected(HollowTestcontainersConfiguration.class),
"Expected HollowTestcontainersConfiguration to be selected but it was not");
assertTrue(HollowTestcontainersConfiguration.available(),
"Expected HollowTestcontainersConfiguration to be available but it was not");
Expand Down Expand Up @@ -88,7 +88,7 @@ public void testEnvVarUnchanged() {

@Test
public void testApplicationURL() {
assertEquals("http://localhost:9080", ApplicationEnvironment.load().getApplicationURL());
assertEquals("http://localhost:9080", ApplicationEnvironment.Resolver.load().getApplicationURL());
}

}

0 comments on commit f22dcfd

Please sign in to comment.