org.finra.herd.dao.Log4jOverridableConfigurerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.finra.herd.dao.Log4jOverridableConfigurerTest.java

Source

/*
* 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.assertTrue;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;

import javax.sql.DataSource;

import org.apache.commons.io.IOUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import org.finra.herd.dao.config.DaoEnvTestSpringModuleConfig;
import org.finra.herd.dao.config.DaoSpringModuleConfig;
import org.finra.herd.model.dto.ConfigurationValue;
import org.finra.herd.model.jpa.ConfigurationEntity;

/**
 * Tests the Log4jOverridableConfigurer class. Note that the tests that setup Log4J configurations in the database are using low level JDBC to ensure data is
 * committed to the database before the test runs. Otherwise, the data wouldn't be visible to the test driver since Log4jOverridableConfigurer uses it's own
 * data source and connection which only sees previously committed data.
 */
public class Log4jOverridableConfigurerTest extends AbstractDaoTest {
    private static final String LOG4J_CONFIG_FILENAME = "classpath:log4jOverridableConfigurer-log4j.xml";
    private static final String LOG4J_CONFIG_NO_CLOB_FILENAME = "classpath:log4jOverridableConfigurerNoClob-log4j.xml";

    private static final String LOG4J_FILENAME_TOKEN = "~log4jFileLocation~";

    private static final Logger LOGGER = LoggerFactory.getLogger(Log4jOverridableConfigurerTest.class);

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testLog4JDbOverrideConfigurationClob() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        String configKey = null;
        try {
            // Create a random configuration key to use when inserting the Log4J configuration into the database.
            configKey = ConfigurationValue.LOG4J_OVERRIDE_CONFIGURATION.getKey()
                    + UUID.randomUUID().toString().substring(0, 5);

            // Insert the Log4J configuration into the database using the CLOB column.
            insertDbLog4JConfigurationFromResourceLocation(LOG4J_CONFIG_FILENAME, 0, outputPath,
                    ConfigurationEntity.COLUMN_VALUE_CLOB, configKey);

            // Shutdown the previously configured logging so we can reinitialize it below.
            loggingHelper.shutdownLogging();

            // Setup the Log4J overridable configurer to use the database location - using the standard CLOB column.
            Log4jOverridableConfigurer log4jConfigurer = getLog4jOverridableConfigurerForDb(configKey);
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // The database override location does exist and should create a log file.
            assertTrue("Log4J output file doesn't exist, but should.", Files.exists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
            deleteDbLog4JConfiguration(configKey);
        }
    }

    @Test
    public void testLog4JDbOverrideConfigurationNoClob() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        String configKey = null;
        try {
            // Create a random configuration key to use when inserting the Log4J configuration into the database.
            configKey = ConfigurationValue.LOG4J_OVERRIDE_CONFIGURATION.getKey()
                    + UUID.randomUUID().toString().substring(0, 5);

            // Insert the Log4J configuration into the database using the non-CLOB column.
            insertDbLog4JConfigurationFromResourceLocation(LOG4J_CONFIG_NO_CLOB_FILENAME, 0, outputPath,
                    ConfigurationEntity.COLUMN_VALUE, configKey);

            // Shutdown the previously configured logging so we can reinitialize it below.
            loggingHelper.shutdownLogging();

            // Setup the Log4J overridable configurer to use the database location, but override the select column to use the non-CLOB column.
            Log4jOverridableConfigurer log4jConfigurer = getLog4jOverridableConfigurerForDb(configKey);
            log4jConfigurer.setSelectColumn(ConfigurationEntity.COLUMN_VALUE);
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // The database override location does exist and should create a log file.
            assertTrue("Log4J output file doesn't exist, but should.", Files.exists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
            deleteDbLog4JConfiguration(configKey);
        }
    }

    @Test
    public void testLog4JDbWithRefreshInterval() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        String configKey = null;
        try {
            // Create a random configuration key to use when inserting the Log4J configuration into the database.
            configKey = ConfigurationValue.LOG4J_OVERRIDE_CONFIGURATION.getKey()
                    + UUID.randomUUID().toString().substring(0, 5);

            // Insert the standard JUnit Log4J configuration that won't create an output file into the database using the CLOB column.
            // We will use a monitoring interval of 1 second to ensure the watch dog thread gets executed.
            insertDbLog4JConfigurationFromResourceLocation(
                    DaoEnvTestSpringModuleConfig.TEST_LOG4J_CONFIG_RESOURCE_LOCATION, 1, outputPath,
                    ConfigurationEntity.COLUMN_VALUE_CLOB, configKey);

            // Shutdown the previously configured logging so we can reinitialize it below.
            loggingHelper.shutdownLogging();

            // Initialize Log4J with a refresh interval of 1/2 second. This will cause Log4J to check for configuration updates every second.
            Log4jOverridableConfigurer log4jConfigurer = getLog4jOverridableConfigurerForDb(configKey);
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // First ensure that the Log4J output file doesn't exist.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));

            // Update the Log4J configuration with one that will create a log file.
            updateDbLog4JConfigurationFromResourceLocation(LOG4J_CONFIG_FILENAME, outputPath,
                    ConfigurationEntity.COLUMN_VALUE_CLOB, configKey);

            // Sleep 3 seconds which will give our watch dog thread and Log4J a chance to read the new configuration file which should create an output file.
            Thread.sleep(3000);

            // Ensure that the Log4J output file now exists.
            assertTrue("Log4J output file doesn't exist, but should.", Files.exists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
            deleteDbLog4JConfiguration(configKey);
        }
    }

    @Test
    public void testLog4JNonExistentOverrideLocation() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write a Log4J configuration file that will create a random output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 0);

            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer
                    .setDefaultResourceLocation(DaoEnvTestSpringModuleConfig.TEST_LOG4J_CONFIG_RESOURCE_LOCATION);
            log4jConfigurer.setOverrideResourceLocation("non_existent_override_location");
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // Since an override location doesn't exist, the default location which doesn't create a log file will get used and the override log file won't get
            // created.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    @Test
    public void testLog4JExistentOverrideLocation() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write a Log4J configuration file that will create a random output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 1);

            loggingHelper.shutdownLogging();

            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer
                    .setDefaultResourceLocation(DaoEnvTestSpringModuleConfig.TEST_LOG4J_CONFIG_RESOURCE_LOCATION);
            log4jConfigurer.setOverrideResourceLocation(configPath.toAbsolutePath().toUri().toURL().toString());
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // The override location does exist and should create a log file.
            assertTrue("Log4J output file doesn't exist, but should.", Files.exists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    @Test
    public void testLog4JNoOverrideLocationOrDefaultLocation() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write a Log4J configuration file that will create a random output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 0);

            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // There is no database, override, or default location. This will display an error, but no logging will be configured.
            // An error will display on system.err which we don't have a way to check so we'll at least ensure that a Log4J output file didn't get created.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    @Test
    public void testLog4JBlankOverrideLocationAndDefaultLocation() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write a Log4J configuration file that will create a random output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 0);

            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer.setDefaultResourceLocation(" ");
            log4jConfigurer.setOverrideResourceLocation(" ");
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // There is a blank default and override location so no log file will get created.
            // An error will display on system.err which we don't have a way to check so we'll at least ensure that a Log4J output file didn't get created.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    @Test
    public void testLog4JNonExistentOverrideLocationAndDefaultLocation() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write a Log4J configuration file that will create a random output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 0);

            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer.setDefaultResourceLocation("non_existent_default_location");
            log4jConfigurer.setOverrideResourceLocation("non_existent_override_location");
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // This is similar to testLog4JNoLocationOrDefaultLocation except we are specifying explicit invalid locations instead of no locations.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    @Test
    @Ignore // This works locally, but fails in Jenkins for some reason. We'll need to investigate at some point.
    public void testLog4JFileWithRefreshInterval() throws Exception {
        Path configPath = getRandomLog4jConfigPath();
        Path outputPath = getRandomLog4jOutputPath();

        try {
            // Write the standard JUnit Log4J configuration that won't create an output file.
            writeFileFromResourceLocation(DaoEnvTestSpringModuleConfig.TEST_LOG4J_CONFIG_RESOURCE_LOCATION,
                    configPath, outputPath, 1);

            // Initialize Log4J with a refresh interval of 1/2 second. This will cause Log4J to check for configuration updates every second.
            Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
            log4jConfigurer.setApplicationContext(applicationContext);
            log4jConfigurer
                    .setDefaultResourceLocation(DaoEnvTestSpringModuleConfig.TEST_LOG4J_CONFIG_RESOURCE_LOCATION);
            log4jConfigurer.setOverrideResourceLocation(configPath.toAbsolutePath().toUri().toURL().toString());
            log4jConfigurer.postProcessBeforeInitialization(null, null);

            // First ensure that the Log4J output file doesn't exist.
            assertTrue("Log4J output file exists, but shouldn't.", Files.notExists(outputPath));

            // Replace the Log4J configuration file with the one that will create an output file.
            writeFileFromResourceLocation(LOG4J_CONFIG_FILENAME, configPath, outputPath, 1);

            // Sleep one second which will give Log4J a chance to read the new configuration file which should create an output file.
            Thread.sleep(3000);

            // Ensure that the Log4J output file now exists.
            assertTrue("Log4J output file doesn't exist, but should.", Files.exists(outputPath));
        } finally {
            cleanup(configPath, outputPath);
        }
    }

    /**
     * Gets a random Log4J configuration path.
     *
     * @return the Log4J configuration path.
     */
    private Path getRandomLog4jConfigPath() {
        return Paths.get(System.getProperty("java.io.tmpdir"), "log4jOverridableConfigurerConfig-log4j-"
                + String.valueOf(UUID.randomUUID()).substring(0, 5) + ".xml");
    }

    /**
     * Gets a random Log4J output path.
     *
     * @return the Log4J output path.
     */
    private Path getRandomLog4jOutputPath() {
        return Paths.get(System.getProperty("java.io.tmpdir"), "log4jOverridableConfigurerOutput-log4j-"
                + String.valueOf(UUID.randomUUID()).substring(0, 5) + ".log");
    }

    /**
     * Reads the contents of the resource location, substitutes the filename token (if it exists), and writes the contents of the resource to the local file
     * system.
     *
     * @param resourceLocation the resource location of the Log4J configuration.
     * @param configPath the Log4J configuration path.
     * @param outputPath the Log4J output path.
     * @param refreshInterval the refresh interval in seconds.
     *
     * @throws Exception if the file couldn't be written.
     */
    private void writeFileFromResourceLocation(String resourceLocation, Path configPath, Path outputPath,
            int refreshInterval) throws Exception {
        // Get the Log4J configuration contents from the classpath file.
        String log4JFileContents = IOUtils.toString(resourceLoader.getResource(resourceLocation).getInputStream());

        // Change the tokenized output filename (if it exists) and replace it with a random filename to support multiple invocations of the JUnit.
        log4JFileContents = log4JFileContents.replace(LOG4J_FILENAME_TOKEN,
                outputPath.toAbsolutePath().toString().replace("\\", "/"));

        // Update the refresh interval to 1 second.
        log4JFileContents = log4JFileContents.replace("monitorInterval=\"0\"",
                "monitorInterval=\"" + refreshInterval + "\"");

        // Write the Log4J configuration to the temporary file.
        try (FileOutputStream fileOutputStream = new FileOutputStream(configPath.toAbsolutePath().toString())) {
            IOUtils.write(log4JFileContents, fileOutputStream);
        }
    }

    /**
     * Reads the contents of the resource location, substitutes the filename token (if it exists), and inserts the contents of the resource to the database.
     *
     * @param resourceLocation the resource location of the Log4J configuration.
     * @param monitorInterval the monitor interval in seconds to use when writing the configuration file to the database.
     * @param outputPath the Log4J output path.
     * @param log4jConfigurationColumn the column name for the Log4J configuration column
     * @param configEntityKey the configuration entity key.
     *
     * @throws Exception if the file contents couldn't be read or the database record couldn't be inserted.
     */
    private void insertDbLog4JConfigurationFromResourceLocation(String resourceLocation, int monitorInterval,
            Path outputPath, String log4jConfigurationColumn, String configEntityKey) throws Exception {
        // Get the Log4J configuration contents from the classpath file.
        String log4JFileContents = IOUtils.toString(resourceLoader.getResource(resourceLocation).getInputStream());

        // Change the tokenized output filename (if it exists) and replace it with a random filename to support multiple invocations of the JUnit.
        log4JFileContents = log4JFileContents.replace(LOG4J_FILENAME_TOKEN,
                outputPath.toAbsolutePath().toString().replace("\\", "/"));

        // Change the monitor interval.
        log4JFileContents = log4JFileContents.replace("monitorInterval=\"0\"",
                "monitorInterval=\"" + String.valueOf(monitorInterval) + "\"");

        // Insert the data.
        String sql = String.format("INSERT INTO %s (%s, %s) VALUES (?,?)", ConfigurationEntity.TABLE_NAME,
                ConfigurationEntity.COLUMN_KEY, log4jConfigurationColumn);
        executePreparedStatement(sql, configEntityKey, log4JFileContents);
    }

    /**
     * Reads the contents of the resource location, substitutes the filename token (if it exists), and inserts the contents of the resource to the database.
     *
     * @param resourceLocation the resource location of the Log4J configuration.
     * @param outputPath the Log4J output path.
     * @param log4jConfigurationColumn the column name for the Log4J configuration column
     * @param configEntityKey the configuration entity key.
     *
     * @throws Exception if the file contents couldn't be read or the database record couldn't be inserted.
     */
    private void updateDbLog4JConfigurationFromResourceLocation(String resourceLocation, Path outputPath,
            String log4jConfigurationColumn, String configEntityKey) throws Exception {
        // Get the Log4J configuration contents from the classpath file.
        String log4JFileContents = IOUtils.toString(resourceLoader.getResource(resourceLocation).getInputStream());

        // Update the refresh interval to 1 second.
        log4JFileContents = log4JFileContents.replace("monitorInterval=\"0\"", "monitorInterval=\"1\"");

        // Change the tokenized output filename (if it exists) and replace it with a random filename to support multiple invocations of the JUnit.
        log4JFileContents = log4JFileContents.replace(LOG4J_FILENAME_TOKEN,
                outputPath.toAbsolutePath().toString().replace("\\", "/"));

        // Update the data.
        String sql = String.format("UPDATE %s SET %s=? WHERE %s=?", ConfigurationEntity.TABLE_NAME,
                log4jConfigurationColumn, ConfigurationEntity.COLUMN_KEY);
        executePreparedStatement(sql, log4JFileContents, configEntityKey);
    }

    /**
     * Deletes the configuration entity record from the database.
     *
     * @param configEntityKey the configuration entity key to delete.
     *
     * @throws SQLException if any SQL errors were encountered.
     */
    private void deleteDbLog4JConfiguration(String configEntityKey) throws SQLException {
        if (configEntityKey != null) {
            String sql = String.format("DELETE FROM %s WHERE %s = ?", ConfigurationEntity.TABLE_NAME,
                    ConfigurationEntity.COLUMN_KEY);
            executePreparedStatement(sql, configEntityKey);
        }
    }

    /**
     * Executes a SQL prepared statement with the specified arguments.
     *
     * @param sql the SQL statement.
     * @param arguments the arguments.
     *
     * @throws SQLException if any SQL errors were encountered.
     */
    private void executePreparedStatement(String sql, Object... arguments) throws SQLException {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            DataSource dataSource = DaoSpringModuleConfig.getHerdDataSource();
            connection = dataSource.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < arguments.length; i++) {
                preparedStatement.setObject(i + 1, arguments[i]);
            }
            preparedStatement.execute();
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }
    }

    /**
     * Gets a newly created Log4J overridable configurer for a database.
     *
     * @param configKey the configuration key to use as the where value.
     *
     * @return the Log4J overridable configurer.
     */
    private Log4jOverridableConfigurer getLog4jOverridableConfigurerForDb(String configKey) {
        Log4jOverridableConfigurer log4jConfigurer = new Log4jOverridableConfigurer();
        log4jConfigurer.setTableName(ConfigurationEntity.TABLE_NAME);
        log4jConfigurer.setSelectColumn(ConfigurationEntity.COLUMN_VALUE_CLOB);
        log4jConfigurer.setWhereColumn(ConfigurationEntity.COLUMN_KEY);
        log4jConfigurer.setWhereValue(configKey);
        log4jConfigurer.setDataSource(DaoSpringModuleConfig.getHerdDataSource());
        log4jConfigurer.setApplicationContext(applicationContext);
        return log4jConfigurer;
    }

    /**
     * Cleanup the Log4J files created.
     *
     * @param configPath the configuration path.
     * @param outputPath the output path.
     *
     * @throws IOException if any problems were encountered while cleaning up the files.
     */
    private void cleanup(Path configPath, Path outputPath) throws IOException {
        // Shutdown the logging which will release the lock on the output file.
        loggingHelper.shutdownLogging();

        // Delete the Log4J configuration we created in the setup.
        if (Files.exists(configPath)) {
            Files.delete(configPath);
        }

        // If we created a Log4J output file (not always), then delete it.
        if (Files.exists(outputPath)) {
            Files.delete(outputPath);
        }
    }
}