com.foundationdb.server.service.config.ConfigurationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.foundationdb.server.service.config.ConfigurationServiceImpl.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2009-2015 FoundationDB, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.foundationdb.server.service.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;

import com.foundationdb.server.error.BadConfigDirectoryException;
import com.foundationdb.server.error.ConfigurationPropertiesLoadException;
import com.foundationdb.server.error.InvalidTimeZoneException;
import com.foundationdb.server.error.MissingRequiredPropertiesException;
import com.foundationdb.server.error.ServiceNotStartedException;
import com.foundationdb.server.error.ServiceAlreadyStartedException;
import com.foundationdb.server.service.Service;
import com.foundationdb.util.tap.Tap;
import com.google.inject.Inject;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConfigurationServiceImpl implements ConfigurationService, Service {
    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationServiceImpl.class);

    private final static String CONFIG_DEFAULTS_RESOURCE = "configuration-defaults.properties";
    private static final String INITIALLY_ENABLED_TAPS = "taps.initiallyenabled";
    private static final String INSTANCE_ID_PROP = "fdbsql.instance_id";

    /** Server properties. Format specified by server. */
    public static final String CONFIG_DIR_PROP = "fdbsql.config_dir"; // Note: Also in GuicedServiceManager
    public static final String SERVER_PROPERTIES_FILE_NAME = "server.properties";

    private volatile Map<String, String> properties = null;
    private final Set<String> requiredKeys = new HashSet<>();

    private volatile long queryTimeoutMilli = -1L; // No timeout

    @Inject
    public ConfigurationServiceImpl() {
    }

    @Override
    public long queryTimeoutMilli() {
        return queryTimeoutMilli;
    }

    @Override
    public void queryTimeoutMilli(long queryTimeoutMilli) {
        this.queryTimeoutMilli = queryTimeoutMilli;
    }

    @Override
    public String getInstanceID() {
        return getProperty(INSTANCE_ID_PROP);
    }

    @Override
    public boolean testing() {
        return false;
    }

    @Override
    public final String getProperty(String propertyName) throws PropertyNotDefinedException {
        String property = internalGetProperty(propertyName);
        if (property == null) {
            throw new PropertyNotDefinedException(propertyName);
        }
        return property;
    }

    private String internalGetProperty(String propertyName) {
        final Map<String, String> map = internalGetProperties();
        return map.get(propertyName);
    }

    @Override
    public Map<String, String> getProperties() {
        Map<String, String> internalProperties = internalGetProperties();
        Map<String, String> results = new TreeMap<>();
        for (Map.Entry<String, String> entry : internalProperties.entrySet()) {
            results.put(entry.getKey(), entry.getValue());
        }
        return Collections.unmodifiableMap(results);
    }

    @Override
    public Properties deriveProperties(String withPrefix) {
        Properties properties = new Properties();
        for (Map.Entry<String, String> configProp : internalGetProperties().entrySet()) {
            String key = configProp.getKey();
            if (key.startsWith(withPrefix)) {
                properties.setProperty(key.substring(withPrefix.length()), configProp.getValue());
            }
        }
        return properties;
    }

    @Override
    public final void start() throws ServiceAlreadyStartedException {
        if (properties == null) {
            properties = internalLoadProperties();
            validateTimeZone();
            String initiallyEnabledTaps = properties.get(INITIALLY_ENABLED_TAPS);
            if (initiallyEnabledTaps != null) {
                Tap.setInitiallyEnabled(initiallyEnabledTaps);
            }
            String id = properties.get(INSTANCE_ID_PROP);
            if ((id == null) || id.isEmpty()) {
                UUID uuid = UUID.randomUUID();
                properties.put(INSTANCE_ID_PROP, uuid.toString());
            }
        }
    }

    private void validateTimeZone() {
        String timezone = System.getProperty("user.timezone");
        // The goal here is to make sure that if the user sets user.timezone we will parse it correctly and not end up
        // in a situation where DateTimeZone is inconsistent with TimeZone, or where it just defaulted to UTC because
        // the user did "-Duser.timezone=America/Los_angeles" (note the lower case a).
        // There are other cases where it would work, such as "-Duser.timezone=PST", but PST isn't a valid timezone
        // according to DateTimeZone, so we will stop, even though it would technically work. That's ok though, because
        // PST is just there for java 1.1.x compatibility.

        // Note also that in the open jdk for 1.7u and 1.8 (at least) there's a bug on Mac OS X where if you specify
        // an invalid user.timezone, it will end up being GMT}05:00 where the 05:00 is your system time, and the } is
        // an unprintable character. I filed a bug though, so that might get fixed.

        // Also note, that, as of this writing, there are usages of both java.util date functionality and Joda
        if (timezone != null && timezone.length() != 0 && DateTimeZone.getProvider().getZone(timezone) == null) {
            // Originally a hard error but JRE on CentOS 6 found to consistently misuse /etc/sysconfig/clock
            // throw new InvalidTimeZoneException();
            LOG.error("Reverting to timezone {} as Joda does not support user.timezone={}",
                    DateTimeZone.getDefault(), timezone);
        } else {
            LOG.debug("Using timezone: {}", DateTimeZone.getDefault());
        }
    }

    @Override
    public final void stop() {
        try {
            unloadProperties();
        } finally {
            properties = null;
        }
    }

    @Override
    public void crash() {
        // Note: do not call unloadProperties().
        properties = null;
    }

    private Map<String, String> internalLoadProperties() throws ServiceAlreadyStartedException {
        Map<String, String> ret = loadProperties();

        Set<String> missingKeys = new HashSet<>();
        for (String required : getRequiredKeys()) {
            if (!ret.containsKey(required)) {
                missingKeys.add(required);
            }
        }
        if (!missingKeys.isEmpty()) {
            throw new MissingRequiredPropertiesException(missingKeys);
        }

        return ret;
    }

    /**
     * Load and return a set of configuration properties. Override this method
     * for customization in unit tests. For example, some unit tests create data
     * files in a temporary directory. These should also override
     * {@link #unloadProperties()} to clean them up.
     * @return the configuration properties
     */
    protected Map<String, String> loadProperties() {
        Properties props = null;

        props = loadResourceProperties(props);
        props = loadSystemProperties(props);
        props = loadConfigDirProperties(props);

        return propertiesToMap(props);
    }

    /**
     * Override this method in unit tests to clean up any temporary files, etc.
     * A class that overrides {@link #loadProperties()} should probably also
     * override this method.
     */
    protected void unloadProperties() {

    }

    protected Set<String> getRequiredKeys() {
        return requiredKeys;
    }

    private static Map<String, String> propertiesToMap(Properties properties) {
        Map<String, String> ret = new HashMap<>();
        for (String keyStr : properties.stringPropertyNames()) {
            String value = properties.getProperty(keyStr);
            ret.put(keyStr, value);
        }
        return ret;
    }

    private static Properties chainProperties(Properties defaults) {
        return defaults == null ? new Properties() : new Properties(defaults);
    }

    private Properties loadResourceProperties(Properties defaults) {
        Properties resourceProps = chainProperties(defaults);

        String resourceName = ConfigurationServiceImpl.class.getPackage().getName().replace(".", "/") + "/"
                + CONFIG_DEFAULTS_RESOURCE;
        Enumeration<URL> e;
        try {
            e = ConfigurationServiceImpl.class.getClassLoader().getResources(resourceName);
        } catch (IOException ex) {
            throw new ConfigurationPropertiesLoadException(CONFIG_DEFAULTS_RESOURCE, ex.getMessage());
        }
        while (e.hasMoreElements()) {
            URL source = e.nextElement();
            try (InputStream resourceIs = source.openStream()) {
                resourceProps.load(resourceIs);
            } catch (IOException ex) {
                throw new ConfigurationPropertiesLoadException(source.toString(), ex.getMessage());
            }
        }
        stripRequiredProperties(resourceProps, requiredKeys);
        return resourceProps;
    }

    static void stripRequiredProperties(Properties properties, Set<String> toSet) {
        Set<String> requiredKeyStrings = new HashSet<>();
        for (String key : properties.stringPropertyNames()) {
            if (key.startsWith("REQUIRED.")) {
                requiredKeyStrings.add(key);
                final String module = key.substring("REQUIRED.".length());
                for (String name : properties.getProperty(key).split(",\\s*")) {
                    toSet.add(module + '.' + name);
                }
            }
        }
        for (String key : requiredKeyStrings) {
            properties.remove(key);
        }
    }

    private static Properties loadSystemProperties(Properties defaults) {
        Properties loadedSystemProps = chainProperties(defaults);
        final Properties actualSystemProps = System.getProperties();
        for (String key : actualSystemProps.stringPropertyNames()) {
            loadedSystemProps.setProperty(key, actualSystemProps.getProperty(key));
        }
        return loadedSystemProps;
    }

    private static Properties loadConfigDirProperties(Properties defaults) {
        Properties combined = chainProperties(defaults);
        String configDirPath = combined.getProperty(CONFIG_DIR_PROP);
        if (configDirPath != null && !"NONE".equals(configDirPath)) {
            File configDir = new File(configDirPath);
            if (!configDir.exists() || !configDir.isDirectory()) {
                throw new BadConfigDirectoryException(configDir.getAbsolutePath());
            }
            try {
                loadFromFile(combined, configDirPath, SERVER_PROPERTIES_FILE_NAME);
            } catch (IOException e) {
                throw new ConfigurationPropertiesLoadException(SERVER_PROPERTIES_FILE_NAME, e.getMessage());
            }
        }
        return combined;
    }

    private static void loadFromFile(Properties props, String directory, String fileName) throws IOException {
        FileInputStream fis = null;
        try {
            File file = new File(directory, fileName);
            fis = new FileInputStream(file);
            props.load(fis);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    private Map<String, String> internalGetProperties() {
        final Map<String, String> ret = properties;
        if (ret == null) {
            throw new ServiceNotStartedException("Configuration");
        }
        return ret;
    }
}