/*
* R Service Bus
*
* Copyright (c) Copyright of Open Analytics NV, 2010-2015
*
* ===========================================================================
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.openanalytics.rsb.config;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.routines.EmailValidator;
import eu.openanalytics.rsb.Util;
import eu.openanalytics.rsb.config.Configuration.ApplicationSecurityAuthorization;
import eu.openanalytics.rsb.config.Configuration.CatalogSection;
import eu.openanalytics.rsb.config.Configuration.DepositDirectoryConfiguration;
import eu.openanalytics.rsb.config.Configuration.DepositEmailConfiguration;
import eu.openanalytics.rsb.config.Configuration.JmxConfiguration;
import eu.openanalytics.rsb.data.FileCatalogManager;
/**
* Loads and validated an RSB configuration file and builds a configuration object out of it.
*
* @author "OpenAnalytics <rsb.development@openanalytics.eu>"
*/
public abstract class ConfigurationFactory
{
public static final String RSB_CONFIGURATION_DIRECTORY = "/etc/rsb";
static final String CONFIGURATION_FILE_NAME_SYSTEM_PROPERTY = Configuration.class.getName();
private static final Log LOGGER = LogFactory.getLog(ConfigurationFactory.class);
private ConfigurationFactory()
{
throw new UnsupportedOperationException("do not instantiate");
}
static boolean isConfigurationPresent()
{
return getConfigurationUrl() != null;
}
public static Configuration loadJsonConfiguration() throws IOException
{
final PersistedConfigurationAdapter pca = load(getConfigurationUrl());
validateConfiguration(pca);
createMissingDirectories(pca);
return pca;
}
private static String getConfigurationFileName()
{
return System.getProperty(CONFIGURATION_FILE_NAME_SYSTEM_PROPERTY,
Configuration.DEFAULT_JSON_CONFIGURATION_FILE);
}
public static Configuration loadJsonConfiguration(final InputStream is) throws IOException
{
final PersistedConfigurationAdapter pca = loadConfigurationStream(null, is);
validateConfiguration(pca);
createMissingDirectories(pca);
return pca;
}
private static void validateConfiguration(final PersistedConfigurationAdapter pca) throws IOException
{
if (BooleanUtils.toBoolean(System.getProperty(ConfigurationFactory.class.getName()
+ ".skipValidation")))
{
LOGGER.info("Skipped validation of: " + pca);
return;
}
final Set<String> validationErrors = validate(pca);
Validate.isTrue(validationErrors.isEmpty(),
"Validation error(s):\n" + StringUtils.join(validationErrors, "\n")
+ "\nfound in configuration:\n" + pca);
LOGGER.info("Successfully validated: " + pca.exportAsJson());
}
// exposed for testing
static PersistedConfigurationAdapter load(final URL configurationUrl) throws IOException
{
Validate.notNull(configurationUrl, "Impossible to find " + configurationUrl);
final InputStream is = configurationUrl.openStream();
return loadConfigurationStream(configurationUrl, is);
}
private static URL getConfigurationUrl()
{
final File configurationFile = new File(RSB_CONFIGURATION_DIRECTORY, getConfigurationFileName());
if (configurationFile.isFile() && configurationFile.canRead())
{
try
{
return configurationFile.toURI().toURL();
}
catch (final MalformedURLException murle)
{
throw new IllegalStateException("Unexpected toURL failure for file: " + configurationFile,
murle);
}
}
else
{
return Thread.currentThread().getContextClassLoader().getResource(getConfigurationFileName());
}
}
private static PersistedConfigurationAdapter loadConfigurationStream(final URL configurationUrl,
final InputStream is)
throws IOException
{
final String json = IOUtils.toString(is);
IOUtils.closeQuietly(is);
final PersistedConfiguration pc = Util.fromJson(json, PersistedConfiguration.class);
final PersistedConfigurationAdapter pca = new PersistedConfigurationAdapter(configurationUrl, pc);
return pca;
}
static Set<String> validate(final PersistedConfigurationAdapter pca) throws IOException
{
final Set<String> validationErrors = new HashSet<String>();
LOGGER.info("Validating configuration: " + pca.getConfigurationUrl());
validateNotNull(pca.getActiveMqWorkDirectory(), "activeMqWorkDirectory", validationErrors);
validateNotNull(pca.getResultsDirectory(), "rsbResultsDirectory", validationErrors);
validateNotNull(pca.getDefaultRserviPoolUri(), "defaultRserviPoolUri", validationErrors);
validateNotNull(pca.getSmtpConfiguration(), "smtpConfiguration", validationErrors);
if (StringUtils.isNotEmpty(pca.getAdministratorEmail()))
{
validateIsTrue(EmailValidator.getInstance().isValid(pca.getAdministratorEmail()),
"if present, the administrator email must be valid", validationErrors);
}
if (pca.getDepositRootDirectories() != null)
{
for (final DepositDirectoryConfiguration depositRootDirectoryConfig : pca.getDepositRootDirectories())
{
final String depositApplicationName = depositRootDirectoryConfig.getApplicationName();
validateIsTrue(Util.isValidApplicationName(depositApplicationName),
"invalid deposit directory application name: " + depositApplicationName, validationErrors);
}
}
if (pca.getDepositEmailAccounts() != null)
{
final FileCatalogManager fileCatalogManager = new FileCatalogManager();
fileCatalogManager.setConfiguration(pca);
for (final DepositEmailConfiguration depositEmailAccount : pca.getDepositEmailAccounts())
{
final String applicationName = depositEmailAccount.getApplicationName();
validateIsTrue(Util.isValidApplicationName(applicationName),
"invalid deposit email application name: " + applicationName, validationErrors);
if (depositEmailAccount.getResponseFileName() != null)
{
final File responseFile = fileCatalogManager.internalGetCatalogFile(
CatalogSection.EMAIL_REPLIES, applicationName,
depositEmailAccount.getResponseFileName());
validateIsTrue(responseFile.exists(), "missing response file: " + responseFile,
validationErrors);
}
if (depositEmailAccount.getJobConfigurationFileName() != null)
{
final File jobConfigurationFile = fileCatalogManager.internalGetCatalogFile(
CatalogSection.JOB_CONFIGURATIONS, applicationName,
depositEmailAccount.getJobConfigurationFileName());
validateIsTrue(jobConfigurationFile.exists(), "missing job configuration file: "
+ jobConfigurationFile, validationErrors);
}
}
}
if (pca.getDataDirectories() != null)
{
for (final File dataDirectoryRoot : pca.getDataDirectories())
{
validateIsTrue(dataDirectoryRoot.isDirectory(), "invalid data directory: "
+ dataDirectoryRoot, validationErrors);
}
}
if (pca.getApplicationSecurityConfiguration() != null)
{
for (final Entry<String, ApplicationSecurityAuthorization> applicationSecurityConfiguration : pca.getApplicationSecurityConfiguration()
.entrySet())
{
validateIsTrue(Util.isValidApplicationName(applicationSecurityConfiguration.getKey()),
"invalid deposit application security authorization application name: "
+ applicationSecurityConfiguration.getKey(), validationErrors);
}
}
if (pca.getJmxConfiguration() != null)
{
final JmxConfiguration jmxConfiguration = pca.getJmxConfiguration();
if (StringUtils.isNotBlank(jmxConfiguration.getHttpAuthenticationUsername()))
{
validateIsTrue(StringUtils.isNotBlank(jmxConfiguration.getHttpAuthenticationPassword()),
"Both username and password must be provided when securing the JMX HTTP console",
validationErrors);
}
}
return validationErrors;
}
private static void validateNotNull(final Object o, final String field, final Set<String> validationErrors)
{
if (o == null)
{
validationErrors.add(field + " can't be null");
}
}
private static void validateIsTrue(final boolean b,
final String message,
final Set<String> validationErrors)
{
if (!b)
{
validationErrors.add(message);
}
}
private static void createMissingDirectories(final PersistedConfigurationAdapter pca) throws IOException
{
if (pca.getDepositRootDirectories() != null)
{
for (final DepositDirectoryConfiguration depositRootDirectoryConfig : pca.getDepositRootDirectories())
{
final File depositRootDir = depositRootDirectoryConfig.getRootDirectory();
FileUtils.forceMkdir(depositRootDir);
FileUtils.forceMkdir(new File(depositRootDir, Configuration.DEPOSIT_ACCEPTED_SUBDIR));
FileUtils.forceMkdir(new File(depositRootDir, Configuration.DEPOSIT_JOBS_SUBDIR));
FileUtils.forceMkdir(new File(depositRootDir, Configuration.DEPOSIT_RESULTS_SUBDIR));
}
}
}
}