diff --git a/base/src/main/java/org/killbill/billing/platform/config/DefaultKillbillConfigSource.java b/base/src/main/java/org/killbill/billing/platform/config/DefaultKillbillConfigSource.java index c62dc2f1..75ad1645 100644 --- a/base/src/main/java/org/killbill/billing/platform/config/DefaultKillbillConfigSource.java +++ b/base/src/main/java/org/killbill/billing/platform/config/DefaultKillbillConfigSource.java @@ -21,8 +21,11 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -68,6 +71,8 @@ public class DefaultKillbillConfigSource implements KillbillConfigSource, OSGICo private static volatile int GMT_WARNING = NOT_SHOWN; private static volatile int ENTROPY_WARNING = NOT_SHOWN; + private final Map> runtimeConfigBySource = new HashMap<>(); + private final Properties properties; public DefaultKillbillConfigSource() throws IOException, URISyntaxException { @@ -88,6 +93,10 @@ public DefaultKillbillConfigSource(@Nullable final String file, final Map entry : extraDefaultProperties.entrySet()) { @@ -96,6 +105,8 @@ public DefaultKillbillConfigSource(@Nullable final String file, final Map> getPropertiesBySource() { + final Map systemProps = new HashMap<>(); + + properties.stringPropertyNames().forEach(key -> systemProps.put(key, properties.getProperty(key))); + + final Map> runtimeBySource = RuntimeConfigRegistry.getAllBySource(); + runtimeBySource.forEach((source, props) -> { + final Map filteredProps = new HashMap<>(); + props.forEach((key, value) -> { + if (!systemProps.containsKey(key)) { + filteredProps.put(key, value); + } + }); + + if (!filteredProps.isEmpty()) { + runtimeConfigBySource.put(source, filteredProps); + } + }); + + runtimeConfigBySource.putAll(runtimeBySource); + + // Returning a shallow copy to satisfy SpotBugs (EI_EXPOSE_REP). + return new HashMap<>(runtimeConfigBySource); + } + private Properties loadPropertiesFromFileOrSystemProperties() { // Chicken-egg problem. It would be nice to have the property in e.g. KillbillServerConfig, // but we need to build the ConfigSource first... final String propertiesFileLocation = System.getProperty(PROPERTIES_FILE); + if (propertiesFileLocation != null) { try { // Ignore System Properties if we're loading from a file final Properties properties = new Properties(); properties.load(UriAccessor.accessUri(propertiesFileLocation)); + + final String category = extractFileNameFromPath(propertiesFileLocation); + + runtimeConfigBySource.put(category, propertiesToMap(properties)); + return properties; } catch (final IOException e) { logger.warn("Unable to access properties file, defaulting to system properties", e); @@ -151,7 +194,11 @@ private Properties loadPropertiesFromFileOrSystemProperties() { } } - return new Properties(System.getProperties()); + final Properties systemProperties = System.getProperties(); + + runtimeConfigBySource.put("SystemProperties", propertiesToMap(systemProperties)); + + return systemProperties; } @VisibleForTesting @@ -209,6 +256,13 @@ protected void populateDefaultProperties() { } } } + + final Map defaultProps = propertiesToMap(defaultProperties); + final Map defaultSystemProps = propertiesToMap(defaultSystemProperties); + + defaultSystemProps.putAll(defaultProps); + + runtimeConfigBySource.put("DefaultSystemProperties", defaultSystemProps); } @VisibleForTesting @@ -244,6 +298,9 @@ protected Properties getDefaultSystemProperties() { private void overrideWithEnvironmentVariables() { // Find all Kill Bill properties in the environment variables final Map env = System.getenv(); + + final Map kbEnvVariables = new HashMap<>(); + for (final Entry entry : env.entrySet()) { if (!entry.getKey().startsWith(ENVIRONMENT_VARIABLE_PREFIX)) { continue; @@ -251,8 +308,12 @@ private void overrideWithEnvironmentVariables() { final String propertyName = fromEnvVariableName(entry.getKey()); final String value = entry.getValue(); + + kbEnvVariables.put(propertyName, value); properties.setProperty(propertyName, value); } + + runtimeConfigBySource.put("EnvironmentVariables", kbEnvVariables); } @VisibleForTesting @@ -313,4 +374,30 @@ private Optional decryptableValue(final String value) { } return Optional.empty(); } + + private String extractFileNameFromPath(String path) { + if (path == null || path.isEmpty()) { + return "unknown.properties"; + } + + if (path.startsWith("file://")) { + path = path.substring("file://".length()); + } + + final Path fileName = Paths.get(path).getFileName(); + if (fileName == null) { + return "unknown.properties"; + } + + return fileName.toString(); + } + + private Map propertiesToMap(final Properties props) { + final Map propertiesMap = new HashMap<>(); + for (final Map.Entry entry : props.entrySet()) { + propertiesMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); + } + + return propertiesMap; + } } diff --git a/base/src/test/java/org/killbill/billing/platform/config/TestDefaultKillbillConfigSource.java b/base/src/test/java/org/killbill/billing/platform/config/TestDefaultKillbillConfigSource.java index 05f44d51..8e6191a0 100644 --- a/base/src/test/java/org/killbill/billing/platform/config/TestDefaultKillbillConfigSource.java +++ b/base/src/test/java/org/killbill/billing/platform/config/TestDefaultKillbillConfigSource.java @@ -27,9 +27,9 @@ import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.jasypt.exceptions.EncryptionOperationNotPossibleException; import org.killbill.billing.osgi.api.OSGIConfigProperties; +import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.testng.Assert; import static org.killbill.billing.platform.config.DefaultKillbillConfigSource.ENVIRONMENT_VARIABLE_PREFIX; @@ -68,6 +68,25 @@ public void testGetProperties() throws URISyntaxException, IOException { Assert.assertEquals(configSource.getProperties().getProperty("1"), "A"); } + @Test(groups = "fast") + public void testGetPropertiesBySource() throws URISyntaxException, IOException { + final Map configuration = new HashMap<>(); + configuration.put("org.killbill.dao.user", "root"); + configuration.put("org.killbill.dao.password", "password"); + + final OSGIConfigProperties configSource = new DefaultKillbillConfigSource(null, configuration); + + final Map> propsBySource = configSource.getPropertiesBySource(); + + Assert.assertNotNull(propsBySource); + Assert.assertFalse(propsBySource.isEmpty()); + + final Map defaultProps = propsBySource.get("ExtraDefaultProperties"); + Assert.assertNotNull(defaultProps); + Assert.assertEquals(defaultProps.get("org.killbill.dao.user"), "root"); + Assert.assertEquals(defaultProps.get("org.killbill.dao.password"), "password"); + } + @Test(groups = "fast") public void testFromEnvVariableName() throws IOException, URISyntaxException { final DefaultKillbillConfigSource configSource = new DefaultKillbillConfigSource(); diff --git a/osgi-api/src/main/java/org/killbill/billing/osgi/api/OSGIConfigProperties.java b/osgi-api/src/main/java/org/killbill/billing/osgi/api/OSGIConfigProperties.java index 53d99fb7..0aa4937b 100644 --- a/osgi-api/src/main/java/org/killbill/billing/osgi/api/OSGIConfigProperties.java +++ b/osgi-api/src/main/java/org/killbill/billing/osgi/api/OSGIConfigProperties.java @@ -19,6 +19,7 @@ package org.killbill.billing.osgi.api; +import java.util.Map; import java.util.Properties; /** @@ -28,13 +29,26 @@ public interface OSGIConfigProperties { /** + * Retrieves the value of the given property. + * * @param propertyName the system property name * @return the value of the property */ public String getString(final String propertyName); /** - * @return all knows system properties (for the JVM) + * Returns all runtime resolved properties as a flat {@link Properties} object. + * + * @return all known configuration properties */ public Properties getProperties(); + + /** + * Returns all runtime resolved properties grouped by their respective source. + * Each key in the outer map represents the source name (e.g., "SystemProperties", "KillbillServerConfig", "CatalogConfig"), + * and the corresponding value is a map of property names to values loaded from that source. + * + * @return a map of configuration sources to their respective key-value property sets. + */ + public Map> getPropertiesBySource(); } diff --git a/osgi-bundles/libs/killbill/src/main/java/org/killbill/billing/osgi/libs/killbill/OSGIConfigPropertiesService.java b/osgi-bundles/libs/killbill/src/main/java/org/killbill/billing/osgi/libs/killbill/OSGIConfigPropertiesService.java index eab5040b..04a5df00 100644 --- a/osgi-bundles/libs/killbill/src/main/java/org/killbill/billing/osgi/libs/killbill/OSGIConfigPropertiesService.java +++ b/osgi-bundles/libs/killbill/src/main/java/org/killbill/billing/osgi/libs/killbill/OSGIConfigPropertiesService.java @@ -19,6 +19,7 @@ package org.killbill.billing.osgi.libs.killbill; +import java.util.Map; import java.util.Properties; import org.killbill.billing.osgi.api.OSGIConfigProperties; @@ -59,4 +60,14 @@ public Properties executeWithService(final OSGIConfigProperties service) { } }); } + + @Override + public Map> getPropertiesBySource() { + return withServiceTracker(killbillTracker, new APICallback<>(OSGIConfigProperties.class.getName()) { + @Override + public Map> executeWithService(final OSGIConfigProperties service) { + return service.getPropertiesBySource(); + } + }); + } } diff --git a/platform-test/src/test/java/org/killbill/billing/beatrix/integration/osgi/util/SetupBundleWithAssertion.java b/platform-test/src/test/java/org/killbill/billing/beatrix/integration/osgi/util/SetupBundleWithAssertion.java index d87a6887..7e5d41e1 100644 --- a/platform-test/src/test/java/org/killbill/billing/beatrix/integration/osgi/util/SetupBundleWithAssertion.java +++ b/platform-test/src/test/java/org/killbill/billing/beatrix/integration/osgi/util/SetupBundleWithAssertion.java @@ -141,7 +141,9 @@ private static void deleteDirectory(final File path, final boolean deleteParent) if (f.isDirectory()) { deleteDirectory(f, true); } - Assert.assertTrue(f.delete(), "Unable to delete file " + f.getAbsolutePath()); + + final boolean deleted = f.delete(); + Assert.assertTrue(deleted || !f.exists(), "Unable to delete file " + f.getAbsolutePath()); } } if (deleteParent) { @@ -177,6 +179,7 @@ private PluginJavaConfig extractJavaBundleTestResource() { return createPluginJavaConfig(resourceUrl.getPath()); } } + return null; } diff --git a/pom.xml b/pom.xml index 1a105ce3..28f06429 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.kill-bill.billing killbill-oss-parent - 0.146.51 + 0.146.56 killbill-platform 0.41.16-SNAPSHOT