Java tutorial
/* * Copyright 2015 herd contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.finra.herd.dao; import static org.junit.Assert.assertEquals; import java.io.File; import java.io.FileOutputStream; import java.util.Enumeration; import java.util.Properties; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.FileConfiguration; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.configuration.reloading.ReloadingStrategy; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.finra.herd.core.helper.LogLevel; /** * Test the reloadable properties source class. This JUnit uses a PropertiesConfiguration as opposed to a DatabaseConfiguration like the real application does * so we can more easily create test scenarios without using a real database and having the create and delete rows with separate connections (i.e. one * connection used by the JUnit data source and one internally created by the DatabaseConfiguration class which could have problems since a single transaction * that can be rolled back won't be possible. */ public class ReloadablePropertySourceTest extends AbstractDaoTest { private static Logger logger = LoggerFactory.getLogger(ReloadablePropertySourceTest.class); public static final String TEST_KEY = "testKey"; public static final String TEST_VALUE_1 = "testValue1"; public static final String TEST_VALUE_2 = "testValue2"; // The time to wait in seconds before the configuration will re-read the properties file. // We want this to be low so the JUnit doesn't take too long to run, but not too low where the file will be re-read faster than the JUnit can execute. // 1 second should be sufficient. public static final long REFRESH_INTERVAL_SECS = 1; // The properties to test with. private Properties properties; // The temporary properties file where the properties will be written to and subsequently read by the reloadable properties source via the specified // configuration. private File propertiesFile; @Before @Override public void setup() throws Exception { super.setup(); // Set the logger to debug level so JUnit coverage will hit all debug only logging. setLogLevel(ReloadablePropertySource.class, LogLevel.DEBUG); logger.info("This test driver outputs debug level logging for code coverage."); // Create the base properties used for each test. properties = new Properties(); properties.put(TEST_KEY, TEST_VALUE_1); // Write the properties to a temporary file (i.e. our configuration store). updatePropertiesFile(); } @After public void tearDown() throws Exception { // Delete the temporary properties file. if (propertiesFile != null) { logger.debug("Deleting file " + propertiesFile.getName()); if (!propertiesFile.delete()) { logger.warn("Unable to delete temporary file: " + propertiesFile.getName()); } } } @Test public void testGetPropertyNoRefreshIntervalConstructor() throws Exception { // Get a reloadable property source that loads properties from the configuration every time a property is read. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(0L); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); } @Test public void testGetPropertyRefreshIntervalConstructor() throws Exception { // Get a reloadable property source that loads properties from the configuration after a configured interval. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(REFRESH_INTERVAL_SECS); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); } @Test public void testGetPropertyValueNotYetRefreshed() throws Exception { // Get a reloadable property source that loads properties from the configuration after a configured interval. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(REFRESH_INTERVAL_SECS); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); // Update the value from value 1 to value 2. updatePropertyToValue2(); // Read the value which should be value 1 still since the refresh interval hasn't passed yet to re-read the properties file. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); } @Ignore // TODO: Test case fails at random times. Need to figure out why. @Test public void testGetPropertyValueRefreshed() throws Exception { // Get a reloadable property source that loads properties from the configuration after a configured interval. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(REFRESH_INTERVAL_SECS); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); // Update the value from value 1 to value 2. updatePropertyToValue2(); // Sleep beyond the refresh interval which will cause our next property get to re-read the properties file. sleepPastRefreshInterval(); // Read the key which should return the new value 2. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_2); } @Ignore // TODO: Test case fails at random times. Need to figure out why. @Test public void testGetPropertyOverrideRefreshInterval() throws Exception { // Update the initial properties file to specify the refresh interval override key. // This will cause the refresh interval to change when the properties are loaded. updatePropertiesFileWithRefreshIntervalOverride(String.valueOf(REFRESH_INTERVAL_SECS)); // Get a reloadable property source that loads properties from the configuration every time a property is read. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(0L); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); // Sleep until the refresh interval has passed which will cause the properties to be loaded again when we get the next property. sleepPastRefreshInterval(); // Re-read the key which should yield the same value since it hasn't changed in the properties file. Reading this again will actually // execute through a piece of code that won't try to re-update the refresh interval that exists in the file since it hasn't changed from it's // previous value. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); // Update the value from value 1 to value 2. updatePropertyToValue2(); // Read the value which should still be the original value since the overridden refresh interval hasn't yet expired. // If the refresh interval override wasn't working, the original configured would force a re-read of the properties file on every property // retrieval which would have read value 2. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); } @Test public void testGetPropertyOverrideWithInvalidRefreshInterval() throws Exception { // Update the initial properties file to specify an invalid refresh interval override key. updatePropertiesFileWithRefreshIntervalOverride("Invalid Interval"); // Get a reloadable property source that loads properties from the configuration every time a property is read. ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(0L); // Read the value which should be the same as what we placed in initially. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); // Update the value from value 1 to value 2. updatePropertyToValue2(); // Read the value which should be the updated value since the configuration currently re-reads the properties file on every property retrieval. // If the refresh interval override changed, this might return value 1 still since the properties file might not have been re-retrieved. verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_2); } /** * Asserts that when a property is requested from the configruation, and it fires an error event (ex. Database is not available), the previously stored * values are not cleared. */ @Test public void testAssertGetPropertyErrorReturnPreviousValue() throws Exception { // Get a reloadable property source that loads properties from the configuration every time a property is read. BaseConfiguration configuration = new BaseConfiguration() { @Override public Object getProperty(String key) { fireError(EVENT_READ_PROPERTY, key, null, new IllegalStateException("test exception")); return null; } }; configuration.addProperty(TEST_KEY, TEST_VALUE_1); ReloadablePropertySource reloadablePropertySource = getNewReloadablePropertiesSource(0L, configuration); verifyPropertySourceValue(reloadablePropertySource, TEST_VALUE_1); } /** * Updates the properties file with the latest version of the properties member variable. * * @throws Exception if the properties file couldn't be updated. */ private void updatePropertiesFile() throws Exception { // Ensure that a properties object exists. if (properties == null) { throw new Exception("Properties can't be written because they don't yet exist."); } // Create a temporary file that we will use to write the properties to. if (propertiesFile == null) { logger.debug("Creating temporary file."); propertiesFile = File.createTempFile(ReloadablePropertySource.class.getSimpleName(), ".properties"); } // Write the properties to the temporary file. try (FileOutputStream fileOutputStream = new FileOutputStream(propertiesFile)) { if (logger.isDebugEnabled()) { logger.debug("Writing properties to " + propertiesFile.getName()); for (Object key : properties.keySet()) { logger.debug("Key [" + key + "] = " + properties.get(key)); } } properties.store(fileOutputStream, "Properties used by " + ReloadablePropertySource.class.getName()); } } /** * Gets a new reloadable properties source object based on the properties member variable. * * @param refreshIntervalSecs the optional refresh interval in seconds. If null, then the default will be used. * * @return the newly created reloadable properties source. * @throws ConfigurationException if the reloadable properties source couldn't be created. */ private ReloadablePropertySource getNewReloadablePropertiesSource(Long refreshIntervalSecs) throws ConfigurationException { return getNewReloadablePropertiesSource(refreshIntervalSecs, getNewPropertiesConfiguration()); } /** * Gets a new reloadable properties source object based on the properties member variable. * * @param refreshIntervalSecs the optional refresh interval in seconds. If null, then the default will be used. * @param configuration A custom configuration * * @return the newly created reloadable properties source. * @throws ConfigurationException if the reloadable properties source couldn't be created. */ private ReloadablePropertySource getNewReloadablePropertiesSource(Long refreshIntervalSecs, Configuration configuration) { return (refreshIntervalSecs == null ? new ReloadablePropertySource(ReloadablePropertySource.class.getName(), cloneProperties(properties), configuration) : new ReloadablePropertySource(ReloadablePropertySource.class.getName(), cloneProperties(properties), configuration, refreshIntervalSecs)); } /** * Creates a clone of the specified properties object. * * @param properties the source properties. * * @return the cloned properties. */ private Properties cloneProperties(Properties properties) { Properties clonedProperties = new Properties(); for (Enumeration<?> propertyNames = properties.propertyNames(); propertyNames.hasMoreElements();) { Object key = propertyNames.nextElement(); clonedProperties.put(key, properties.get(key)); } return clonedProperties; } /** * Gets a new properties configuration that will re-load the properties from a file every time it is called. * * @return the properties configuration. * @throws ConfigurationException if the properties configuration couldn't be created. */ private PropertiesConfiguration getNewPropertiesConfiguration() throws ConfigurationException { // Create a new properties configuration. // We are using this instead of a database configuration for easier testing. PropertiesConfiguration propertiesConfiguration = new PropertiesConfiguration(propertiesFile); // Create a reloading strategy that will always reload when asked. // There were some problems using the FileChangedReloadingStrategy where it wasn't detecting changed files and causing some methods in this // JUnit to fail. propertiesConfiguration.setReloadingStrategy(new ReloadingStrategy() { @Override public void setConfiguration(FileConfiguration configuration) { } @Override public void init() { } @Override public boolean reloadingRequired() { // Tell the caller that the properties should always be reloaded. return true; } @Override public void reloadingPerformed() { } }); return propertiesConfiguration; } /** * Updates the key in the persistent property store to "value 2". * * @throws Exception if the property store couldn't be updated. */ private void updatePropertyToValue2() throws Exception { properties.put(TEST_KEY, TEST_VALUE_2); updatePropertiesFile(); } /** * Reads the test key from the reloadable property source and verifies that it is set to the specified expected value. * * @param reloadablePropertySource the reloadable property source. * @param expectedValue the expected value. * * @throws IllegalArgumentException if the value isn't the same as the expected value. */ private void verifyPropertySourceValue(ReloadablePropertySource reloadablePropertySource, String expectedValue) { logger.debug("Reading key " + TEST_KEY + " and expecting value " + expectedValue); logger.debug("Properties file value is " + properties.get(TEST_KEY) + " and reloadable property source value is " + reloadablePropertySource.getProperty(TEST_KEY)); String value = (String) reloadablePropertySource.getProperty(TEST_KEY); assertEquals(expectedValue, value); } /** * Sleeps for a duration that is equal to the refresh interval which will ensure the refresh interval has passed. * * @throws Exception if we couldn't sleep. */ private void sleepPastRefreshInterval() throws Exception { logger.debug("Sleeping for " + REFRESH_INTERVAL_SECS + " second(s)."); Thread.sleep(REFRESH_INTERVAL_SECS * 1000); } /** * Updates the persistent properties file with a specified refresh interval override value. * * @param refreshIntervalSecs the refresh interval. * * @throws Exception if the properties file couldn't be updated. */ private void updatePropertiesFileWithRefreshIntervalOverride(String refreshIntervalSecs) throws Exception { // Update the initial properties file to specify an invalid refresh interval override key. properties.put(ReloadablePropertySource.REFRESH_INTERVAL_SECS_OVERRIDE_KEY, refreshIntervalSecs); updatePropertiesFile(); } }