From 4479a8e69509585ed97a199be4d947d2c2f46a3c Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 1 Sep 2020 22:44:50 +0200 Subject: [PATCH] WIP: load SentryOptions from external sources. --- .../location/CompoundResourceLocator.java | 40 +++++++++ .../ConfigurationResourceLocator.java | 17 ++++ .../location/EnvironmentBasedLocator.java | 37 ++++++++ .../config/location/StaticFileLocator.java | 34 +++++++ .../SystemPropertiesBasedLocator.java | 37 ++++++++ .../CompoundConfigurationProvider.java | 34 +++++++ .../provider/ConfigurationProvider.java | 18 ++++ .../EnvironmentConfigurationProvider.java | 27 ++++++ .../provider/JndiConfigurationProvider.java | 88 +++++++++++++++++++ .../core/config/provider/JndiSupport.java | 26 ++++++ .../LocatorBasedConfigurationProvider.java | 30 +++++++ .../provider/SentryOptionsProvider.java | 83 +++++++++++++++++ ...SystemPropertiesConfigurationProvider.java | 46 ++++++++++ .../resource/CompoundResourceLoader.java | 44 ++++++++++ .../ContextClassLoaderResourceLoader.java | 18 ++++ .../provider/resource/FileResourceLoader.java | 34 +++++++ .../provider/resource/ResourceLoader.java | 20 +++++ .../ResourceLoaderConfigurationProvider.java | 77 ++++++++++++++++ .../provider/SentryOptionsProviderTest.kt | 12 +++ .../src/test/resources/sentry.properties | 1 + 20 files changed, 723 insertions(+) create mode 100644 sentry-core/src/main/java/io/sentry/core/config/location/CompoundResourceLocator.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/location/ConfigurationResourceLocator.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/location/EnvironmentBasedLocator.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/location/StaticFileLocator.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/location/SystemPropertiesBasedLocator.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/CompoundConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/ConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/EnvironmentConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/JndiConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/JndiSupport.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/LocatorBasedConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/SentryOptionsProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/SystemPropertiesConfigurationProvider.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/resource/CompoundResourceLoader.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/resource/ContextClassLoaderResourceLoader.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/resource/FileResourceLoader.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoader.java create mode 100644 sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoaderConfigurationProvider.java create mode 100644 sentry-core/src/test/java/io/sentry/core/config/provider/SentryOptionsProviderTest.kt create mode 100644 sentry-core/src/test/resources/sentry.properties diff --git a/sentry-core/src/main/java/io/sentry/core/config/location/CompoundResourceLocator.java b/sentry-core/src/main/java/io/sentry/core/config/location/CompoundResourceLocator.java new file mode 100644 index 000000000..fa4f1f19c --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/location/CompoundResourceLocator.java @@ -0,0 +1,40 @@ +package io.sentry.core.config.location; + +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Wraps multiple resource locators and returns the first non-null configuration file path. + */ +public final class CompoundResourceLocator implements ConfigurationResourceLocator { + private final Collection locators; + + /** + * Instantiates a new compound configuration resource locator. + * @param locators the locators to iterate through + */ + public CompoundResourceLocator(ConfigurationResourceLocator ... locators) { + this.locators = Arrays.asList(locators); + } + + /** + * Tries to find the location of the resource containing the Sentry configuration file. + * + * @return the first non-null configuration file path (in the iteration order of the collection provided to + * the constructor) or null if none such exists. + */ + @Override + @Nullable + public String getConfigurationResourcePath() { + for (ConfigurationResourceLocator l : locators) { + String path = l.getConfigurationResourcePath(); + if (path != null) { + return path; + } + } + + return null; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/location/ConfigurationResourceLocator.java b/sentry-core/src/main/java/io/sentry/core/config/location/ConfigurationResourceLocator.java new file mode 100644 index 000000000..af23bab47 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/location/ConfigurationResourceLocator.java @@ -0,0 +1,17 @@ +package io.sentry.core.config.location; + +import org.jetbrains.annotations.Nullable; + +/** + * Tries to find the Sentry configuration file. + */ +public interface ConfigurationResourceLocator { + /** + * Tries to find the location of the resource containing the Sentry configuration file. + * + * @return the location on which some {@link io.sentry.core.config.provider.resource.ResourceLoader} can find the configuration file or null if this + * locator could not find any. + */ + @Nullable + String getConfigurationResourcePath(); +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/location/EnvironmentBasedLocator.java b/sentry-core/src/main/java/io/sentry/core/config/location/EnvironmentBasedLocator.java new file mode 100644 index 000000000..f680ced35 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/location/EnvironmentBasedLocator.java @@ -0,0 +1,37 @@ +package io.sentry.core.config.location; + +import org.jetbrains.annotations.Nullable; + +/** + * Tries to find the location of the Sentry configuration file in some environment variable. + */ +public final class EnvironmentBasedLocator implements ConfigurationResourceLocator { + /** + * The default environment variable to use for obtaining the location of the Sentry configuration file. + */ + public static final String DEFAULT_ENV_VAR_NAME = "SENTRY_PROPERTIES_FILE"; + + private final String envVarName; + + /** + * Constructs a new instance that will use the environment variable defined in {@link #DEFAULT_ENV_VAR_NAME}. + */ + public EnvironmentBasedLocator() { + this(DEFAULT_ENV_VAR_NAME); + } + + /** + * Constructs a new instance that will use the provided environment variable. + * + * @param envVarName the name of the environment variable to use + */ + public EnvironmentBasedLocator(String envVarName) { + this.envVarName = envVarName; + } + + @Override + @Nullable + public String getConfigurationResourcePath() { + return System.getenv(envVarName); + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/location/StaticFileLocator.java b/sentry-core/src/main/java/io/sentry/core/config/location/StaticFileLocator.java new file mode 100644 index 000000000..ddf322ba3 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/location/StaticFileLocator.java @@ -0,0 +1,34 @@ +package io.sentry.core.config.location; + +/** + * Provides the configuration file location using a file name provided at instantiation time. + */ +public final class StaticFileLocator implements ConfigurationResourceLocator { + /** + * The default file name of the Sentry configuration file. + */ + public static final String DEFAULT_FILE_PATH = "sentry.properties"; + + private final String path; + + /** + * Constructs a new instance using the {@link #DEFAULT_FILE_PATH}. + */ + public StaticFileLocator() { + this(DEFAULT_FILE_PATH); + } + + /** + * Constructs a new instance that will return the provided path as the path of the Sentry configuration. + * + * @param filePath the path to the Sentry configuration + */ + public StaticFileLocator(String filePath) { + this.path = filePath; + } + + @Override + public String getConfigurationResourcePath() { + return path; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/location/SystemPropertiesBasedLocator.java b/sentry-core/src/main/java/io/sentry/core/config/location/SystemPropertiesBasedLocator.java new file mode 100644 index 000000000..109dad199 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/location/SystemPropertiesBasedLocator.java @@ -0,0 +1,37 @@ +package io.sentry.core.config.location; + +import org.jetbrains.annotations.Nullable; + +/** + * Tries to find the location of the Sentry configuration file using the value of some system property. + */ +public final class SystemPropertiesBasedLocator implements ConfigurationResourceLocator { + /** + * The default system property to use for obtaining the location of the Sentry configuration file. + */ + public static final String DEFAULT_PROPERTY_NAME = "sentry.properties.file"; + + private final String propertyName; + + /** + * Constructs a new instance that will use the {@link #DEFAULT_PROPERTY_NAME}. + */ + public SystemPropertiesBasedLocator() { + this(DEFAULT_PROPERTY_NAME); + } + + /** + * Constructs a new instance that will use the provided system property name. + * + * @param propertyName the name of the property to load the location of the Sentry configuration file from + */ + public SystemPropertiesBasedLocator(String propertyName) { + this.propertyName = propertyName; + } + + @Override + @Nullable + public String getConfigurationResourcePath() { + return System.getProperty(propertyName); + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/CompoundConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/CompoundConfigurationProvider.java new file mode 100644 index 000000000..0191348dc --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/CompoundConfigurationProvider.java @@ -0,0 +1,34 @@ +package io.sentry.core.config.provider; + +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * Wraps a couple of other configuration providers to act as one, returning the first non-null value for given + * configuration key, in the iteration order of the wrapped providers. + */ +final class CompoundConfigurationProvider implements ConfigurationProvider { + private final Collection providers; + + /** + * Instantiates the new compound provider by wrapping the provided collection of providers. + * @param providers the providers to wrap + */ + public CompoundConfigurationProvider(Collection providers) { + this.providers = providers; + } + + @Nullable + @Override + public String getProperty(String key) { + for (ConfigurationProvider p : providers) { + String val = p.getProperty(key); + if (val != null) { + return val; + } + } + + return null; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/ConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/ConfigurationProvider.java new file mode 100644 index 000000000..fc0314983 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/ConfigurationProvider.java @@ -0,0 +1,18 @@ +package io.sentry.core.config.provider; + +import org.jetbrains.annotations.Nullable; + +/** + * Sentry is able to load configuration from various sources. This interface is implemented by each one of them. + */ +public interface ConfigurationProvider { + + /** + * Returns the value of the configuration property with the provided key. + * + * @param key the name of the configuration property + * @return the value of the property as found in this provider or null if none found + */ + @Nullable + String getProperty(String key); +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/EnvironmentConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/EnvironmentConfigurationProvider.java new file mode 100644 index 000000000..266f3cbef --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/EnvironmentConfigurationProvider.java @@ -0,0 +1,27 @@ +package io.sentry.core.config.provider; + +import org.jetbrains.annotations.Nullable; + +/** + * Tries to find the configuration properties in the environment. + */ +final class EnvironmentConfigurationProvider implements ConfigurationProvider { + /** + * The prefix of the environment variables holding Sentry configuration. + */ + public static final String ENV_VAR_PREFIX = "SENTRY_"; + +// private static final Logger logger = LoggerFactory.getLogger(EnvironmentConfigurationProvider.class); + + @Nullable + @Override + public String getProperty(String key) { + String ret = System.getenv(ENV_VAR_PREFIX + key.replace(".", "_").toUpperCase()); + + if (ret != null) { +// logger.debug("Found {}={} in System Environment Variables.", key, ret); + } + + return ret; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/JndiConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/JndiConfigurationProvider.java new file mode 100644 index 000000000..58ec628f4 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/JndiConfigurationProvider.java @@ -0,0 +1,88 @@ +package io.sentry.core.config.provider; + + +import io.sentry.core.ILogger; +import io.sentry.core.SentryLevel; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.NoInitialContextException; + +/** + * A configuration provider that looks up the configuration in the JNDI registry. + * + * @see JndiSupport to check whether Jndi is available in the current JVM without needing to load this class + */ +final class JndiConfigurationProvider implements ConfigurationProvider { + /** + * The default prefix of the JNDI names of Sentry configuration. This, concatenated with the configuration key name + * provided to the {@link #getProperty(String)} method, must form a valid JNDI name. + */ + public static final String DEFAULT_JNDI_PREFIX = "java:comp/env/sentry/"; + + private final String prefix; + private final JndiContextProvider contextProvider; + private final ILogger logger; + + /** + * Constructs a new instance using the {@link #DEFAULT_JNDI_PREFIX} and {@link JndiContextProvider} that returns + * a new {@link InitialContext} each time it is asked for a context. This ensures that any changes in the JNDI + * environment are taken into account on the next configuration property lookup. + */ + public JndiConfigurationProvider(ILogger logger) { + this(DEFAULT_JNDI_PREFIX, new JndiContextProvider() { + @Override + public Context getContext() throws NamingException { + return new InitialContext(); + } + }, logger); + } + + /** + * Constructs a new instance that will look up the Sentry configuration keys using the provided prefix and using + * the context obtained from the JNDI context provider. Because the JNDI environment is dynamic, Sentry uses the + * JNDI context provider to obtain a fresh copy of the environment (if the provider chooses to return such). + * @param jndiNamePrefix The prefix of the JNDI names of Sentry configuration. This, concatenated with + * the configuration key name provided to the {@link #getProperty(String)} method, must form + * a valid JNDI name. + * @param contextProvider an object able to provide instances of the JNDI context + * @param logger the logger + */ + public JndiConfigurationProvider(String jndiNamePrefix, JndiContextProvider contextProvider, ILogger logger) { + this.prefix = jndiNamePrefix; + this.contextProvider = contextProvider; + this.logger = logger; + } + + @Override + public String getProperty(String key) { + String value = null; + try { + Context ctx = contextProvider.getContext(); + value = (String) ctx.lookup(prefix + key); + } catch (NoInitialContextException e) { + logger.log(SentryLevel.DEBUG, "JNDI not configured for Sentry (NoInitialContextEx)"); + } catch (NamingException e) { + logger.log(SentryLevel.DEBUG, "No " + prefix + key + " in JNDI"); + } catch (RuntimeException e) { + logger.log(SentryLevel.WARNING, "Odd RuntimeException while testing for JNDI", e); + } + return value; + } + + /** + * A helper interface to be able to obtain application-specific JNDI context during Sentry configuration lookup. + */ + public interface JndiContextProvider { + /** + * Returns the context to use when looking for a property in JNDI. Note that it is supposed that this method + * returns a new context each time, but that might not be required or even correct depending on that + * requirements of the supplier of the implementation of this interface. + * + * @return a possibly new instance of the JNDI context + * @throws NamingException on JNDI error + */ + Context getContext() throws NamingException; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/JndiSupport.java b/sentry-core/src/main/java/io/sentry/core/config/provider/JndiSupport.java new file mode 100644 index 000000000..07bcbc939 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/JndiSupport.java @@ -0,0 +1,26 @@ +package io.sentry.core.config.provider; + + +/** + * A helper class to check whether JNDI is available in the current JVM. + */ +final class JndiSupport { +// private static final Logger logger = LoggerFactory.getLogger(JndiSupport.class); + + private JndiSupport() { + } + + /** + * Checks whether JNDI is available. + * @return true if JNDI is available, false otherwise + */ + public static boolean isAvailable() { + try { + Class.forName("javax.naming.InitialContext", false, JndiSupport.class.getClassLoader()); + return true; + } catch (ClassNotFoundException | NoClassDefFoundError e) { +// logger.trace("JNDI is not available: " + e.getMessage()); + return false; + } + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/LocatorBasedConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/LocatorBasedConfigurationProvider.java new file mode 100644 index 000000000..4f0bb37db --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/LocatorBasedConfigurationProvider.java @@ -0,0 +1,30 @@ +package io.sentry.core.config.provider; + +import io.sentry.core.ILogger; +import io.sentry.core.config.provider.resource.ResourceLoader; +import io.sentry.core.config.location.ConfigurationResourceLocator; +import io.sentry.core.config.provider.resource.ResourceLoaderConfigurationProvider; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Similar to {@link ResourceLoaderConfigurationProvider} but uses a {@link ConfigurationResourceLocator} to find + * the path to the configuration file. + */ +final class LocatorBasedConfigurationProvider extends ResourceLoaderConfigurationProvider { + /** + * Instantiates a new configuration provider using the parameters. + * + * @param rl the resource loader to load the contents of the configuration file with + * @param locator the locator to find the configuration file with + * @param charset the charset of the configuration file + * @param logger the logger + * @throws IOException on failure to process the configuration file + */ + public LocatorBasedConfigurationProvider(ResourceLoader rl, ConfigurationResourceLocator locator, Charset charset, @NotNull ILogger logger) + throws IOException { + super(rl, locator.getConfigurationResourcePath(), charset, logger); + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/SentryOptionsProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/SentryOptionsProvider.java new file mode 100644 index 000000000..a3389412e --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/SentryOptionsProvider.java @@ -0,0 +1,83 @@ +package io.sentry.core.config.provider; + +import io.sentry.core.ILogger; +import io.sentry.core.NoOpLogger; +import io.sentry.core.SentryOptions; +import io.sentry.core.SystemOutLogger; +import io.sentry.core.config.location.CompoundResourceLocator; +import io.sentry.core.config.location.EnvironmentBasedLocator; +import io.sentry.core.config.location.StaticFileLocator; +import io.sentry.core.config.location.SystemPropertiesBasedLocator; +import io.sentry.core.config.provider.resource.CompoundResourceLoader; +import io.sentry.core.config.provider.resource.ContextClassLoaderResourceLoader; +import io.sentry.core.config.provider.resource.FileResourceLoader; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +public final class SentryOptionsProvider { + private final ConfigurationProvider configurationProvider; + + public SentryOptionsProvider(ConfigurationProvider configurationProvider) { + this.configurationProvider = configurationProvider; + } + + public static SentryOptionsProvider create(boolean debug) { + final ILogger logger = debug ? new SystemOutLogger() : NoOpLogger.getInstance(); + List providers = new ArrayList<>(); + + if (JndiSupport.isAvailable()) { + providers.add(new JndiConfigurationProvider(logger)); + } + + providers.add(new SystemPropertiesConfigurationProvider()); + providers.add(new EnvironmentConfigurationProvider()); + + try { + providers.add( + new LocatorBasedConfigurationProvider( + new CompoundResourceLoader( + new FileResourceLoader(), + new ContextClassLoaderResourceLoader() + ), + new CompoundResourceLocator( + new SystemPropertiesBasedLocator(), + new EnvironmentBasedLocator(), + new StaticFileLocator() + ), + Charset.defaultCharset(), + logger + ) + ); + } catch (IOException e) { +// logger.debug("Failed to instantiate resource locator-based configuration provider.", e); + } + + return new SentryOptionsProvider(new CompoundConfigurationProvider(providers)); + } + + public @NotNull SentryOptions resolve() { + final SentryOptions options = new SentryOptions(); + + final String dsn = configurationProvider.getProperty("dsn"); + if (dsn != null) { + options.setDsn(dsn); + } + + final Long shutdownTimeout = getLong("shutdownTimeout"); + if (shutdownTimeout != null) { + options.setShutdownTimeout(shutdownTimeout); + } + + return options; + } + + private @Nullable Long getLong(@NotNull String property) { + final String value = configurationProvider.getProperty(property); + return value != null ? Long.valueOf(value) : null; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/SystemPropertiesConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/SystemPropertiesConfigurationProvider.java new file mode 100644 index 000000000..4219e5695 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/SystemPropertiesConfigurationProvider.java @@ -0,0 +1,46 @@ +package io.sentry.core.config.provider; + +import org.jetbrains.annotations.Nullable; + +/** + * A configuration provider that loads the configuration from the system properties. + */ +final class SystemPropertiesConfigurationProvider implements ConfigurationProvider { + /** + * The default prefix of the system properties that should be considered Sentry configuration. + */ + public static final String DEFAULT_SYSTEM_PROPERTY_PREFIX = "sentry."; + +// private static final Logger logger = LoggerFactory.getLogger(SystemPropertiesConfigurationProvider.class); + + private final String prefix; + + /** + * Constructs a new instance using the {@link #DEFAULT_SYSTEM_PROPERTY_PREFIX}. + */ + public SystemPropertiesConfigurationProvider() { + this(DEFAULT_SYSTEM_PROPERTY_PREFIX); + } + + /** + * Constructs a new instance that will locate the configuration properties from the system properties having + * the provided prefix. + * + * @param systemPropertyPrefix the prefix of the system properties that should be considered Sentry configuration + */ + public SystemPropertiesConfigurationProvider(String systemPropertyPrefix) { + this.prefix = systemPropertyPrefix; + } + + @Nullable + @Override + public String getProperty(String key) { + String ret = System.getProperty(prefix + key.toLowerCase()); + + if (ret != null) { +// logger.debug("Found {}={} in Java System Properties.", key, ret); + } + + return ret; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/resource/CompoundResourceLoader.java b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/CompoundResourceLoader.java new file mode 100644 index 000000000..8aa4507f1 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/CompoundResourceLoader.java @@ -0,0 +1,44 @@ +package io.sentry.core.config.provider.resource; + +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; + +/** + * A compound resource loader that returns the first non-null stream of the list of the loaders provided at + * the instantiation time. + */ +public final class CompoundResourceLoader implements ResourceLoader { + private final Collection loaders; + + /** + * Instantiates new resource loader using the provided loaders. + * + * @param loaders the loaders to wrap + */ + public CompoundResourceLoader(ResourceLoader ... loaders) { + this.loaders = Arrays.asList(loaders); + } + + /** + * This goes through the wrapped resource loaders in the iteration order and returns the first non-null input stream + * obtained from the wrapped resource loaders. + * + * @param filepath Path of the resource to open + * @return the input stream with the contents of the resource on given path or null if none could be found + */ + @Nullable + @Override + public InputStream getInputStream(String filepath) { + for (ResourceLoader l : loaders) { + InputStream is = l.getInputStream(filepath); + if (is != null) { + return is; + } + } + + return null; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ContextClassLoaderResourceLoader.java b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ContextClassLoaderResourceLoader.java new file mode 100644 index 000000000..beb07d908 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ContextClassLoaderResourceLoader.java @@ -0,0 +1,18 @@ +package io.sentry.core.config.provider.resource; + +import java.io.InputStream; + +/** + * A {@link ResourceLoader} that considers the paths to be resource locations in the context classloader of the current + * thread. + */ +public final class ContextClassLoaderResourceLoader implements ResourceLoader { + @Override + public InputStream getInputStream(String filepath) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + return classLoader.getResourceAsStream(filepath); + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/resource/FileResourceLoader.java b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/FileResourceLoader.java new file mode 100644 index 000000000..2a811c19c --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/FileResourceLoader.java @@ -0,0 +1,34 @@ +package io.sentry.core.config.provider.resource; + +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * A {@link ResourceLoader} that considers the paths to be filesystem locations. + */ +public final class FileResourceLoader implements ResourceLoader { +// private static final Logger logger = LoggerFactory.getLogger(io.sentry.config.FileResourceLoader.class); + + @Nullable + @Override + public InputStream getInputStream(String filepath) { + File f = new File(filepath); + if (f.isFile() && f.canRead()) { + try { + return new FileInputStream(f); + } catch (FileNotFoundException e) { +// logger.debug("Configuration file {} could not be found even though we just checked it can be read...", +// filepath); + } + } else { +// logger.debug("The configuration file {} (which resolves to absolute path {}) doesn't exist, is not a file" +// + " or is not readable.", f, f.getAbsolutePath()); + } + + return null; + } +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoader.java b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoader.java new file mode 100644 index 000000000..6be120ebe --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoader.java @@ -0,0 +1,20 @@ +package io.sentry.core.config.provider.resource; + +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; + +/** + * Interface for platform-specific resource loaders. + */ +public interface ResourceLoader { + /** + * Returns an InputStream from the resource at {@code filepath} or null, if it was not possible + * to open the resource. + * + * @param filepath Path of the resource to open + * @return Resource's input stream or null + */ + @Nullable + InputStream getInputStream(String filepath); +} diff --git a/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoaderConfigurationProvider.java b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoaderConfigurationProvider.java new file mode 100644 index 000000000..bd8725be7 --- /dev/null +++ b/sentry-core/src/main/java/io/sentry/core/config/provider/resource/ResourceLoaderConfigurationProvider.java @@ -0,0 +1,77 @@ +package io.sentry.core.config.provider.resource; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.core.ILogger; +import io.sentry.core.SentryLevel; +import io.sentry.core.config.provider.ConfigurationProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Properties; + +/** + * A configuration provider that loads the properties from a {@link ResourceLoader}. + */ +@Open +public class ResourceLoaderConfigurationProvider implements ConfigurationProvider { + + @Nullable + private final Properties properties; + + @NotNull + private final ILogger logger; + + /** + * Instantiates a new resource loader based configuration provider. + * @param rl the resource loader used to load the configuration file + * @param filePath the path to the configuration file as understood by the resource loader + * @param charset the charset of the configuration file + * @param logger the logger + * @throws IOException on failure to process the configuration file contents + */ + public ResourceLoaderConfigurationProvider(ResourceLoader rl, @Nullable String filePath, Charset charset, @NotNull ILogger logger) + throws IOException { + this.logger = logger; + properties = loadProperties(rl, filePath, charset); + } + + @Nullable + private static Properties loadProperties(ResourceLoader rl, @Nullable String filePath, Charset charset) + throws IOException { + if (filePath == null) { + return null; + } + + InputStream is = rl.getInputStream(filePath); + + if (is == null) { + return null; + } + + try (InputStreamReader rdr = new InputStreamReader(is, charset)) { + Properties props = new Properties(); + props.load(rdr); + return props; + } + } + + @Override + public String getProperty(String key) { + if (properties == null) { + return null; + } + + String ret = properties.getProperty(key); + + if (ret != null) { + logger.log(SentryLevel.DEBUG, "Found %s=%s in properties file.", key, ret); + } + + return ret; + } + +} diff --git a/sentry-core/src/test/java/io/sentry/core/config/provider/SentryOptionsProviderTest.kt b/sentry-core/src/test/java/io/sentry/core/config/provider/SentryOptionsProviderTest.kt new file mode 100644 index 000000000..0f2319deb --- /dev/null +++ b/sentry-core/src/test/java/io/sentry/core/config/provider/SentryOptionsProviderTest.kt @@ -0,0 +1,12 @@ +package io.sentry.core.config.provider + +import kotlin.test.Test + +class SentryOptionsProviderTest { + + @Test + fun `loads properties from external source`() { + val options = SentryOptionsProvider.create(true).resolve() + println(options.dsn) + } +} diff --git a/sentry-core/src/test/resources/sentry.properties b/sentry-core/src/test/resources/sentry.properties new file mode 100644 index 000000000..98f3b11e3 --- /dev/null +++ b/sentry-core/src/test/resources/sentry.properties @@ -0,0 +1 @@ +dsn=popopopo