All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jitsi.impl.configuration.ConfigurationServiceImpl Maven / Gradle / Ivy

/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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.jitsi.impl.configuration;

import java.beans.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;

import org.apache.commons.lang3.*;
import org.apache.commons.lang3.StringUtils;
import org.jitsi.impl.configuration.xml.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.fileaccess.*;
import org.jitsi.service.libjitsi.*;
import org.jitsi.util.*;
import org.jitsi.util.xml.*;
import org.jitsi.utils.*;
import org.jitsi.utils.logging.*;

/**
 * A straightforward implementation of the ConfigurationService using
 * an XML or a .properties file for storing properties. Currently only
 * String properties are meaningfully saved (we should probably
 * consider how and whether we should take care of the rest).
 *
 * @author Emil Ivov
 * @author Damian Minkov
 * @author Lyubomir Marinov
 * @author Dmitri Melnikov
 * @author Pawel Domas
 */
public class ConfigurationServiceImpl
    implements ConfigurationService
{
    /**
     * The Logger used by this ConfigurationServiceImpl
     * instance for logging output.
     */
    private final Logger logger
        = Logger.getLogger(ConfigurationServiceImpl.class);

    /**
     * The name of the ConfigurationStore class to be used as the
     * default when no specific ConfigurationStore class is determined
     * as necessary.
     */
    private static final String DEFAULT_CONFIGURATION_STORE_CLASS_NAME
        = "net.java.sip.communicator.impl.configuration"
            + ".SQLiteConfigurationStore";

    /**
     * Name of the system file name property.
     */
    private static final String SYS_PROPS_FILE_NAME_PROPERTY
        = "net.java.sip.communicator.SYS_PROPS_FILE_NAME";

    /**
     * Name of the file containing default properties.
     */
    private static final String DEFAULT_PROPS_FILE_NAME
                                         = "jitsi-defaults.properties";

    /**
     * Name of the file containing overrides (possibly set by the deployer)
     * for any of the default properties.
     */
    private static final String DEFAULT_OVERRIDES_PROPS_FILE_NAME
                                             = "jitsi-default-overrides.properties";

    /**
     * Specify names of command line arguments which are password, so that their
     * values will be masked when 'sun.java.command' is printed to the logs.
     * Separate each name with a comma.
     */
    public static String PASSWORD_CMD_LINE_ARGS;

    /**
     * Set this filed value to a regular expression which will be used to select
     * system properties keys whose values should be masked when printed out to
     * the logs.
     */
    public static String PASSWORD_SYS_PROPS;

    /**
     * A reference to the currently used configuration file.
     */
    private File configurationFile = null;

    /**
     * A set of immutable properties deployed with the application during
     * install time. The properties in this file will be impossible to override
     * and attempts to do so will simply be ignored.
     * @see #defaultProperties
     */
    private Map immutableDefaultProperties = new HashMap<>();

    /**
     * A set of properties deployed with the application during install time.
     * Contrary to the properties in {@link #immutableDefaultProperties} the
     * ones in this map can be overridden with call to the
     * setProperty() methods. Still, re-setting one of these properties
     * to null would cause for its initial value to be restored.
     */
    private Map defaultProperties = new HashMap<>();

    /**
     * Our event dispatcher.
     */
    private final ChangeEventDispatcher changeEventDispatcher
        = new ChangeEventDispatcher(this);

    /**
     * A (cached) reference to a FileAccessService implementation used
     * by this ConfigurationService implementation.
     */
    private FileAccessService faService;

    /**
     * The indicator which determines whether this instance has assigned a value
     * to {@link #faService}. Introduced in order to avoid multiple attempts to
     * query for a FileAccessService implementation while still
     * delaying the initial query.
     */
    private boolean faServiceIsAssigned = false;

    /**
     * The ConfigurationStore implementation which contains the
     * property name-value associations of this
     * ConfigurationService and performs their actual storing in
     * configurationFile.
     */
    private ConfigurationStore store;

    public ConfigurationServiceImpl()
    {
        /*
         * XXX We explicitly delay the query for the FileAccessService
         * implementation because FileAccessServiceImpl looks for properties set
         * by methods of ConfigurationServiceImpl and we want to make sure that
         * we have given the chance to this ConfigurationServiceImpl to set
         * these properties before FileAccessServiceImpl looks for them.
         */

        try
        {
            debugPrintSystemProperties();
            preloadSystemPropertyFiles();
            loadDefaultProperties();
            reloadConfiguration();
        }
        catch (IOException ex)
        {
            logger.error("Failed to load the configuration file", ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setProperty(String propertyName, Object property)
        throws ConfigPropertyVetoException
    {
        setProperty(propertyName, property, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setProperty(String propertyName,
                            Object property,
                            boolean isSystem)
        throws ConfigPropertyVetoException
    {
        Object oldValue = getProperty(propertyName);

        //first check whether the change is ok with everyone
        if (changeEventDispatcher.hasVetoableChangeListeners(propertyName))
            changeEventDispatcher.fireVetoableChange(
                propertyName, oldValue, property);

        //no exception was thrown - lets change the property and fire a
        //change event

        if (logger.isTraceEnabled())
            logger.trace(propertyName + "( oldValue=" + oldValue
                     + ", newValue=" + property + ".");

        doSetProperty(propertyName, property, isSystem);

        try
        {
            storeConfiguration();
        }
        catch (IOException ex)
        {
            logger.error(
                "Failed to store configuration after a property change");
        }

        if (changeEventDispatcher.hasPropertyChangeListeners(propertyName))
            changeEventDispatcher.firePropertyChange(
                propertyName, oldValue, property);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setProperties(Map properties)
        throws ConfigPropertyVetoException
    {
        //first check whether the changes are ok with everyone
        Map oldValues = new HashMap<>(properties.size());
        for (Map.Entry property : properties.entrySet())
        {
            String propertyName = property.getKey();
            Object oldValue = getProperty(propertyName);

            oldValues.put(propertyName, oldValue);

            if (changeEventDispatcher.hasVetoableChangeListeners(propertyName))
                changeEventDispatcher
                    .fireVetoableChange(
                        propertyName,
                        oldValue,
                        property.getValue());
        }

        for (Map.Entry property : properties.entrySet())
            doSetProperty(property.getKey(), property.getValue(), false);

        try
        {
            storeConfiguration();
        }
        catch (IOException ex)
        {
            logger.error(
                "Failed to store configuration after property changes");
        }

        for (Map.Entry property : properties.entrySet())
        {
            String propertyName = property.getKey();

            if (changeEventDispatcher.hasPropertyChangeListeners(propertyName))
                changeEventDispatcher
                    .firePropertyChange(
                        propertyName,
                        oldValues.get(propertyName),
                        property.getValue());
        }
    }

    /**
     * Performs the actual setting of a property with a specific name to a
     * specific new value without asking VetoableChangeListener,
     * storing into the configuration file and notifying
     * {@link PropertyChangeListener}s.
     *
     * @param propertyName the name of the property which is to be set to a
     * specific value.
     * @param property the value to be assigned to the property with the
     * specified name
     * @param isSystem true if the property with the specified name is
     * to be set as a system property; false, otherwise
     */
    private void doSetProperty(
        String propertyName, Object property, boolean isSystem)
    {
        //once set system, a property remains system even if the user
        //specified sth else

        if (isSystemProperty(propertyName))
            isSystem = true;

        // ignore requests to override immutable properties:
        if (immutableDefaultProperties.containsKey(propertyName))
            return;

        if (property == null)
        {
            store.removeProperty(propertyName);

            if (isSystem)
            {
                //we can't remove or nullset a sys prop so let's "empty" it.
                System.setProperty(propertyName, "");
            }
        }
        else
        {
            if (isSystem)
            {
                //in case this is a system property, we must only store it
                //in the System property set and keep only a ref locally.
                System.setProperty(propertyName, property.toString());
                store.setSystemProperty(propertyName);
            }
            else
            {
                store.setNonSystemProperty(propertyName, property);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeProperty(String propertyName)
    {
        List childPropertyNames =
            getPropertyNamesByPrefix(propertyName, false);

        //remove all properties
        for (String pName : childPropertyNames)
        {
            removePropertyInternal(pName);
        }

        try
        {
            storeConfiguration();
        }
        catch (IOException ex)
        {
            logger.error("Failed to store configuration after "
                + "a property change");
        }
    }

    /**
     * Removes the property with the specified name. Calling
     * this method would first trigger a PropertyChangeEvent that will
     * be dispatched to all VetoableChangeListeners. In case no complaints
     * (PropertyVetoException) have been received, the property will be actually
     * changed and a PropertyChangeEvent will be dispatched.
     * All properties with prefix propertyName will also be removed.
     * 

* Does not store anything. * * @param propertyName the name of the property to change. */ private void removePropertyInternal(String propertyName) { Object oldValue = getProperty(propertyName); //first check whether the change is ok with everyone if (changeEventDispatcher.hasVetoableChangeListeners(propertyName)) changeEventDispatcher.fireVetoableChange( propertyName, oldValue, null); //no exception was thrown - lets change the property and fire a //change event if (logger.isTraceEnabled()) logger.trace("Will remove prop: " + propertyName + "."); store.removeProperty(propertyName); if (changeEventDispatcher.hasPropertyChangeListeners(propertyName)) changeEventDispatcher.firePropertyChange( propertyName, oldValue, null); } /** * {@inheritDoc} */ @Override public Object getProperty(String propertyName) { Object result = immutableDefaultProperties.get(propertyName); if (result != null) return result; result = store.getProperty(propertyName); if (result != null) return result; return defaultProperties.get(propertyName); } /** * {@inheritDoc} */ @Override public List getAllPropertyNames() { return Collections.unmodifiableList( Arrays.asList(store.getPropertyNames())); } /** * {@inheritDoc} */ @Override public List getPropertyNamesByPrefix(String prefix, boolean exactPrefixMatch) { HashSet resultKeySet = new HashSet<>(); //first fill in the names from the immutable default property set Set propertyNameSet; String[] namesArray; if(immutableDefaultProperties.size() > 0) { propertyNameSet = immutableDefaultProperties.keySet(); namesArray = propertyNameSet.toArray( new String[propertyNameSet.size()] ); getPropertyNamesByPrefix(prefix, exactPrefixMatch, namesArray, resultKeySet); } //now get property names from the current store. getPropertyNamesByPrefix(prefix, exactPrefixMatch, store.getPropertyNames(), resultKeySet); //finally, get property names from mutable default property set. if(defaultProperties.size() > 0) { propertyNameSet = defaultProperties.keySet(); namesArray = propertyNameSet.toArray(new String[propertyNameSet.size()]); getPropertyNamesByPrefix(prefix, exactPrefixMatch, namesArray, resultKeySet); } return new ArrayList<>(resultKeySet); } /** * Updates the specified String resulSet to contain all * property names in the names array that partially or completely * match the specified prefix. Depending on the value of the * exactPrefixMatch parameter the method will (when false) * or will not (when exactPrefixMatch is true) include property names that * have prefixes longer than the specified prefix param. * * @param prefix a String containing the prefix (the non dotted non-caps * part of a property name) that we're looking for. * @param exactPrefixMatch a boolean indicating whether the returned * property names should all have a prefix that is an exact match of the * the prefix param or whether properties with prefixes that * contain it but are longer than it are also accepted. * @param names the list of names that we'd like to search. * * @return a reference to the updated result set. */ private Set getPropertyNamesByPrefix( String prefix, boolean exactPrefixMatch, String[] names, Set resultSet) { for (String key : names) { if(exactPrefixMatch) { int ix = key.lastIndexOf('.'); if(ix == -1) continue; String keyPrefix = key.substring(0, ix); if(prefix.equals(keyPrefix)) resultSet.add(key); } else { if(key.startsWith(prefix)) resultSet.add(key); } } return resultSet; } /** * {@inheritDoc} */ @Override public List getPropertyNamesBySuffix(String suffix) { List resultKeySet = new LinkedList<>(); for (String key : store.getPropertyNames()) { int ix = key.lastIndexOf('.'); if (ix != -1 && suffix.equals(key.substring(ix + 1))) resultKeySet.add(key); } return resultKeySet; } /** * {@inheritDoc} */ @Override public void addPropertyChangeListener(PropertyChangeListener listener) { changeEventDispatcher.addPropertyChangeListener(listener); } /** * {@inheritDoc} */ @Override public void removePropertyChangeListener(PropertyChangeListener listener) { changeEventDispatcher.removePropertyChangeListener(listener); } /** * {@inheritDoc} */ @Override public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeEventDispatcher. addPropertyChangeListener(propertyName, listener); } /** * {@inheritDoc} */ @Override public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeEventDispatcher. removePropertyChangeListener(propertyName, listener); } /** * {@inheritDoc} */ @Override public void addVetoableChangeListener(ConfigVetoableChangeListener listener) { changeEventDispatcher.addVetoableChangeListener(listener); } /** * {@inheritDoc} */ @Override public void removeVetoableChangeListener(ConfigVetoableChangeListener listener) { changeEventDispatcher.removeVetoableChangeListener(listener); } /** * {@inheritDoc} */ @Override public void addVetoableChangeListener(String propertyName, ConfigVetoableChangeListener listener) { changeEventDispatcher.addVetoableChangeListener(propertyName, listener); } /** * {@inheritDoc} */ @Override public void removeVetoableChangeListener(String propertyName, ConfigVetoableChangeListener listener) { changeEventDispatcher.removeVetoableChangeListener(propertyName, listener); } /** * {@inheritDoc} */ @Override public void reloadConfiguration() throws IOException { this.configurationFile = null; File file = getConfigurationFile(); if (file != null) { FileAccessService faService = getFileAccessService(); if (faService != null) { // Restore the file if necessary. FailSafeTransaction trans = faService.createFailSafeTransaction(file); try { trans.restoreFile(); } catch (Exception e) { logger.error( "Failed to restore configuration file " + file, e); } } } try { store.reloadConfiguration(file); } catch (XMLException xmle) { throw new IOException(xmle); } } /** * {@inheritDoc} */ @Override public synchronized void storeConfiguration() throws IOException { storeConfiguration(getConfigurationFile()); } /** * Stores local properties in the specified configuration file. * * @param file a reference to the configuration file where properties should * be stored. * @throws IOException if there was a problem writing to the specified file. */ private void storeConfiguration(File file) throws IOException { // If the configuration file is forcibly considered read-only, do not // write it. String readOnly = System.getProperty(PNAME_CONFIGURATION_FILE_IS_READ_ONLY); if ((readOnly != null) && Boolean.parseBoolean(readOnly)) return; // write the file. FailSafeTransaction trans = null; if (file != null) { FileAccessService faService = getFileAccessService(); if (faService != null) trans = faService.createFailSafeTransaction(file); } Throwable exception = null; try { if (trans != null) trans.beginTransaction(); try (OutputStream stream = (file == null) ? null : new FileOutputStream(file)) { store.storeConfiguration(stream); } if (trans != null) trans.commit(); } catch (IllegalStateException | IOException e) { exception = e; } if (exception != null) { logger.error( "can't write data in the configuration file", exception); if (trans != null) trans.rollback(); } } /** * {@inheritDoc} */ @Override public String getConfigurationFilename() { try { File file = getConfigurationFile(); if(file != null) return file.getName(); } catch(IOException ex) { logger.error("Error loading configuration file", ex); } return null; } /** * Returns the configuration file currently used by the implementation. * If there is no such file or this is the first time we reference it * a new one is created. * @return the configuration File currently used by the implementation. */ private File getConfigurationFile() throws IOException { if (this.configurationFile == null) { createConfigurationFile(); /* * Make sure that the properties SC_HOME_DIR_LOCATION and * SC_HOME_DIR_NAME are available in the store of this instance so * that users don't have to ask the system properties again. */ getScHomeDirLocation(); getScHomeDirName(); } return this.configurationFile; } /** * Determines the name and the format of the configuration file to be used * and initializes the {@link #configurationFile} and {@link #store} fields * of this instance. */ private void createConfigurationFile() throws IOException { /* * Choose the format of the configuration file so that the * performance-savvy properties format is used whenever possible and * only go with the slow and fat XML format when necessary. */ File configurationFile = getConfigurationFile("xml", false); if (configurationFile == null) { /* * It's strange that there's no configuration file name but let it * play out as it did when the configuration file was in XML format. */ setConfigurationStore(XMLConfigurationStore.class); } else { /* * Figure out the format of the configuration file by looking at its * extension. */ String name = configurationFile.getName(); int extensionBeginIndex = name.lastIndexOf('.'); String extension = (extensionBeginIndex > -1) ? name.substring(extensionBeginIndex) : null; /* * Obviously, a file with the .properties extension is in the * properties format. Since there's no file with the .xml extension, * the case is simple. */ if (".properties".equalsIgnoreCase(extension)) { this.configurationFile = configurationFile; if (!(this.store instanceof PropertyConfigurationStore)) this.store = new PropertyConfigurationStore(); } else { /* * But if we're told that the configuration file name is with * the .xml extension, we may also have a .properties file or * the .xml extension may be only the default and not forced on * us so it may be fine to create a .properties file and use the * properties format anyway. */ File newConfigurationFile = new File( configurationFile.getParentFile(), ((extensionBeginIndex > -1) ? name.substring(0, extensionBeginIndex) : name) + ".properties"); /* * If there's an actual file with the .properties extension, * then we've previously migrated the configuration from the XML * format to the properties format. We may have failed to delete * the migrated .xml file but it's fine because the .properties * file is there to signal that we have to use it instead of the * .xml file. */ if (newConfigurationFile.exists()) { this.configurationFile = newConfigurationFile; if (!(this.store instanceof PropertyConfigurationStore)) this.store = new PropertyConfigurationStore(); } /* * Otherwise, the lack of an existing .properties file doesn't * help us much and we have the .xml extension for the file name * so we have to determine whether it's just the default or it's * been forced on us. */ else if (getSystemProperty(PNAME_CONFIGURATION_FILE_NAME) == null) { Class defaultConfigurationStoreClass = getDefaultConfigurationStoreClass(); /* * The .xml is not forced on us so we allow ourselves to not * obey the default and use the properties format. If a * configuration file in the XML format exists already, we * have to migrate it to the properties format. */ if (configurationFile.exists()) { ConfigurationStore xmlStore = new XMLConfigurationStore(); try { xmlStore.reloadConfiguration(configurationFile); } catch (XMLException xmlex) { throw new IOException(xmlex); } setConfigurationStore(defaultConfigurationStoreClass); if (this.store != null) copy(xmlStore, this.store); Throwable exception = null; try { storeConfiguration(this.configurationFile); } catch (IllegalStateException | IOException e) { exception = e; } if (exception == null) { configurationFile.delete(); } else { this.configurationFile = configurationFile; this.store = xmlStore; } } else { setConfigurationStore(defaultConfigurationStoreClass); } } else { /* * The .xml extension is forced on us so we have to assume * that whoever forced it knows what she wants to get so we * have to obey and use the XML format. */ this.configurationFile = configurationFile.exists() ? configurationFile : getConfigurationFile("xml", true); if (!(this.store instanceof XMLConfigurationStore)) this.store = new XMLConfigurationStore(); } } } } /** * {@inheritDoc} */ @Override public String getScHomeDirLocation() { //first let's check whether we already have the name of the directory //set as a configuration property String scHomeDirLocation = null; if (store != null) scHomeDirLocation = getString(PNAME_SC_HOME_DIR_LOCATION); if (scHomeDirLocation == null) { //no luck, check whether user has specified a custom name in the //system properties scHomeDirLocation = getSystemProperty(PNAME_SC_HOME_DIR_LOCATION); if (scHomeDirLocation == null) scHomeDirLocation = getSystemProperty("user.home"); //now save all this as a configuration property so that we don't //have to look for it in the sys props next time and so that it is //available for other bundles to consult. if (store != null) { store.setNonSystemProperty( PNAME_SC_HOME_DIR_LOCATION, scHomeDirLocation); } } return scHomeDirLocation; } /** * {@inheritDoc} */ @Override public String getScHomeDirName() { //first let's check whether we already have the name of the directory //set as a configuration property String scHomeDirName = null; if (store != null) scHomeDirName = getString(PNAME_SC_HOME_DIR_NAME); if (scHomeDirName == null) { //no luck, check whether user has specified a custom name in the //system properties scHomeDirName = getSystemProperty(PNAME_SC_HOME_DIR_NAME); if (scHomeDirName == null) scHomeDirName = ".sip-communicator"; //now save all this as a configuration property so that we don't //have to look for it in the sys props next time and so that it is // available for other bundles to consult. if (store != null) store .setNonSystemProperty( PNAME_SC_HOME_DIR_NAME, scHomeDirName); } return scHomeDirName; } /** * Returns a reference to the configuration file that the service should * load. The method would try to load a file with the name * sip-communicator.xml unless a different one is specified in the system * property net.java.sip.communicator.PROPERTIES_FILE_NAME . The method * would first try to load the file from the current directory if it exists * this is not the case a load would be attempted from the * $HOME/.sip-communicator directory. In case it was not found there either * we'll look for it in all locations currently present in the $CLASSPATH. * In case we find it in there we will copy it to the * $HOME/.sip-communicator directory in case it was in a jar archive and * return the reference to the newly created file. In case the file is to be * found nowhere - a new empty file in the user home directory and returns a * link to that one. * * @param extension * the extension of the file name of the configuration file. The * specified extension may not be taken into account if the the * configuration file name is forced through a system property. * @param create * true to create the configuration file with the * determined file name if it does not exist; false to * only figure out the file name of the configuration file * without creating it * @return the configuration file currently used by the implementation. */ private File getConfigurationFile(String extension, boolean create) throws IOException { //see whether we have a user specified name for the conf file String pFileName = getSystemProperty(PNAME_CONFIGURATION_FILE_NAME); if (pFileName == null) pFileName = "sip-communicator." + extension; // try to open the file in current directory File configFileInCurrentDir = new File(pFileName); if (configFileInCurrentDir.exists()) { if (logger.isDebugEnabled()) logger.debug( "Using config file in current dir: " + configFileInCurrentDir.getAbsolutePath()); return configFileInCurrentDir; } // we didn't find it in ".", try the SIP Communicator home directory // first check whether a custom SC home directory is specified File configDir = new File( getScHomeDirLocation() + File.separator + getScHomeDirName()); File configFileInUserHomeDir = new File(configDir, pFileName); if (configFileInUserHomeDir.exists()) { if (logger.isDebugEnabled()) logger.debug( "Using config file in $HOME/.sip-communicator: " + configFileInUserHomeDir.getAbsolutePath()); return configFileInUserHomeDir; } // If we are in a jar - copy config file from jar to user home. InputStream in = getClass().getClassLoader().getResourceAsStream(pFileName); //Return an empty file if there wasn't any in the jar //null check report from John J. Barton - IBM if (in == null) { if (create) { configDir.mkdirs(); configFileInUserHomeDir.createNewFile(); } if (logger.isDebugEnabled()) logger.debug( "Created an empty file in $HOME: " + configFileInUserHomeDir.getAbsolutePath()); return configFileInUserHomeDir; } if (logger.isTraceEnabled()) logger.trace( "Copying config file from JAR into " + configFileInUserHomeDir.getAbsolutePath()); configDir.mkdirs(); try { copy(in, configFileInUserHomeDir); } finally { try { in.close(); } catch (IOException ioex) { /* * Ignore it because it doesn't matter and, most importantly, it * shouldn't prevent us from using the configuration file. */ } } return configFileInUserHomeDir; } /** * Gets the ConfigurationStore Class to be used as the * default when no specific ConfigurationStore Class is * determined as necessary. * * @return the ConfigurationStore Class to be used as the * default when no specific ConfigurationStore Class is * determined as necessary */ @SuppressWarnings("unchecked") private static Class getDefaultConfigurationStoreClass() { Class defaultConfigurationStoreClass = null; if (DEFAULT_CONFIGURATION_STORE_CLASS_NAME != null) { Class clazz = null; try { clazz = Class.forName(DEFAULT_CONFIGURATION_STORE_CLASS_NAME); } catch (ClassNotFoundException cnfe) { } if ((clazz != null) && ConfigurationStore.class.isAssignableFrom(clazz)) defaultConfigurationStoreClass = (Class) clazz; } if (defaultConfigurationStoreClass == null) defaultConfigurationStoreClass = PropertyConfigurationStore.class; return defaultConfigurationStoreClass; } private static void copy(ConfigurationStore src, ConfigurationStore dest) { for (String name : src.getPropertyNames()) if (src.isSystemProperty(name)) dest.setSystemProperty(name); else dest.setNonSystemProperty(name, src.getProperty(name)); } /** * Copies the contents of a specific InputStream as bytes into * a specific output File. * * @param inputStream * the InputStream the contents of which is to be * output in the specified File * @param outputFile * the File to write the contents of the specified * InputStream into * @throws IOException */ private static void copy(InputStream inputStream, File outputFile) throws IOException { try (OutputStream outputStream = new FileOutputStream(outputFile)) { byte[] bytes = new byte[4 * 1024]; int bytesRead; while ((bytesRead = inputStream.read(bytes)) != -1) outputStream.write(bytes, 0, bytesRead); } } /** * Returns the value of the specified java system property. In case the * value was a zero length String or one that only contained whitespaces, * null is returned. This method is for internal use only. Users of the * configuration service are to use the getProperty() or getString() methods * which would automatically determine whether a property is system or not. * @param propertyName the name of the property whose value we need. * @return the value of the property with name propertyName or null if * the value had length 0 or only contained spaces tabs or new lines. */ private static String getSystemProperty(String propertyName) { String retval = System.getProperty(propertyName); if ((retval != null) && (retval.trim().length() == 0)) retval = null; return retval; } /** * Returns the String value of the specified property (minus all * encompassing whitespaces) and null in case no property value was mapped * against the specified propertyName, or in case the returned property * string had zero length or contained whitespaces only. * * @param propertyName the name of the property that is being queried. * @return the result of calling the property's toString method and null in * case there was no value mapped against the specified. * propertyName, or the returned string had zero length or * contained whitespaces only. */ @Override public String getString(String propertyName) { Object propValue = getProperty(propertyName); if (propValue == null) return null; String propStrValue = propValue.toString().trim(); return (propStrValue.length() > 0) ? propStrValue : null; } /** * {@inheritDoc} */ @Override public String getString(String propertyName, String defaultValue) { String value = getString(propertyName); return value != null ? value : defaultValue; } /** * {@inheritDoc} */ @Override public boolean getBoolean(String propertyName, boolean defaultValue) { String stringValue = getString(propertyName); return (stringValue == null) ? defaultValue : Boolean .parseBoolean(stringValue); } /** * Gets a (cached) reference to a FileAccessService implementation * to be used by this ConfigurationService implementation. * * @return a (cached) reference to a FileAccessService * implementation */ private synchronized FileAccessService getFileAccessService() { if (faService == null && !faServiceIsAssigned) { faService = LibJitsi.getFileAccessService(); faServiceIsAssigned = true; } return faService; } /** * {@inheritDoc} */ @Override public int getInt(String propertyName, int defaultValue) { String stringValue = getString(propertyName); int intValue = defaultValue; if ((stringValue != null) && (stringValue.length() > 0)) { try { intValue = Integer.parseInt(stringValue); } catch (NumberFormatException ex) { logger.error(propertyName + " does not appear to be an integer. " + "Defaulting to " + defaultValue + ".", ex); } } return intValue; } /** * {@inheritDoc} */ @Override public double getDouble(String propertyName, double defaultValue) { String stringValue = getString(propertyName); double doubleValue = defaultValue; if ((stringValue != null) && (stringValue.length() > 0)) { try { doubleValue = Double.parseDouble(stringValue); } catch (NumberFormatException ex) { logger.error(propertyName + " does not appear to be a double. " + "Defaulting to " + defaultValue + ".", ex); } } return doubleValue; } /** * {@inheritDoc} */ @Override public long getLong(String propertyName, long defaultValue) { String stringValue = getString(propertyName); long longValue = defaultValue; if ((stringValue != null) && (stringValue.length() > 0)) { try { longValue = Long.parseLong(stringValue); } catch (NumberFormatException ex) { logger.error( propertyName + " does not appear to be a long integer. " + "Defaulting to " + defaultValue + ".", ex); } } return longValue; } /** * Determines whether the property with the specified * propertyName has been previously declared as System * * @param propertyName the name of the property to verify * @return true if someone at some point specified that property to be * system. (This could have been either through a call to * setProperty(string, true)) or by setting the system attribute in the * xml conf file to true. */ private boolean isSystemProperty(String propertyName) { return store.isSystemProperty(propertyName); } /** * Deletes the configuration file currently used by this implementation. */ @Override public void purgeStoredConfiguration() { if (configurationFile != null) { configurationFile.delete(); configurationFile = null; } if (store != null) for (String name : store.getPropertyNames()) store.removeProperty(name); } /** * Goes over all system properties and outputs their names and values for * debug purposes. The method has no effect if the logger is at a log level * other than DEBUG or TRACE (FINE or FINEST). * * Changed that system properties are printed in INFO level and this way * they are included in the beginning of every users log file. */ private void debugPrintSystemProperties() { if (!logger.isInfoEnabled()) return; logger.info(ConfigUtils.getSystemPropertiesDebugString()); } /** * {@inheritDoc} */ @Override public void logConfigurationProperties(String excludePattern) { if (!logger.isInfoEnabled()) return; Pattern exclusion = null; if (StringUtils.isNotEmpty(excludePattern)) { exclusion = Pattern.compile( excludePattern, Pattern.CASE_INSENSITIVE); } for (String p : getAllPropertyNames()) { Object v = getProperty(p); // Not sure if this can happen, but just in case... if (v == null) continue; if (exclusion != null && exclusion.matcher(p).find()) { v = "**********"; } logger.info(p + "=" + v); } } /** * The method scans the contents of the SYS_PROPS_FILE_NAME_PROPERTY where * it expects to find a comma separated list of names of files that should * be loaded as system properties. The method then parses these files and * loads their contents as system properties. All such files have to be in * a location that's in the classpath. */ private void preloadSystemPropertyFiles() { String propertyFilesListStr = System.getProperty( SYS_PROPS_FILE_NAME_PROPERTY ); if(propertyFilesListStr == null || propertyFilesListStr.trim().length() == 0) return; StringTokenizer tokenizer = new StringTokenizer(propertyFilesListStr, ";,", false); while( tokenizer.hasMoreTokens()) { String fileName = tokenizer.nextToken(); try { fileName = fileName.trim(); Properties fileProps = new Properties(); try (InputStream stream = ClassLoader .getSystemResourceAsStream(fileName)) { fileProps.load(stream); } // now set all of this file's properties as system properties for (Map.Entry entry : fileProps.entrySet()) System.setProperty((String) entry.getKey(), (String) entry .getValue()); } catch (Exception ex) { //this is an insignificant method that should never affect //the rest of the application so we'll afford ourselves to //kind of silence all possible exceptions (which would most //often be IOExceptions). We will however log them in case //anyone would be interested. logger.error("Failed to load property file: " + fileName, ex); } } } /** * Specifies the configuration store that this instance of the configuration * service implementation must use. * * @param clazz the {@link ConfigurationStore} that this configuration * service instance instance has to use. * * @throws IOException if loading properties from the specified store fails. */ private void setConfigurationStore( Class clazz) throws IOException { String extension = null; if (PropertyConfigurationStore.class.isAssignableFrom(clazz)) extension = "properties"; else if (XMLConfigurationStore.class.isAssignableFrom(clazz)) extension = "xml"; this.configurationFile = (extension == null) ? null : getConfigurationFile(extension, true); if (!clazz.isInstance(this.store)) { Throwable exception = null; try { this.store = clazz.newInstance(); } catch (IllegalAccessException | InstantiationException e) { exception = e; } if (exception != null) throw new RuntimeException(exception); } } /** * Loads the default property maps from the Jitsi installation directory * then overrides them with the default override values. */ private void loadDefaultProperties() { loadDefaultProperties(DEFAULT_PROPS_FILE_NAME); loadDefaultProperties(DEFAULT_OVERRIDES_PROPS_FILE_NAME); } /** * Tests whether the application has been launched using Java WebStart */ private boolean isLaunchedByWebStart() { boolean hasJNLP; try { Class.forName("javax.jnlp.ServiceManager"); hasJNLP = true; } catch (ClassNotFoundException ex) { hasJNLP = false; } String jwsVersion = System.getProperty("javawebstart.version"); if(jwsVersion != null && jwsVersion.length() > 0) { hasJNLP = true; } return hasJNLP; } /** * Loads the specified default properties maps from the Jitsi installation * directory. Typically this file is to be called for the default properties * and the admin overrides. * * @param fileName the name of the file we need to load. */ private void loadDefaultProperties(String fileName) { try { Properties fileProps = new Properties(); InputStream fileStream; if(OSUtils.IS_ANDROID) { fileStream = getClass().getClassLoader() .getResourceAsStream(fileName); } else if(isLaunchedByWebStart()) { logger.info("WebStart classloader"); fileStream = Thread.currentThread().getContextClassLoader() .getResourceAsStream(fileName); } else { logger.info("Normal classloader"); fileStream = ClassLoader.getSystemResourceAsStream(fileName); } if(fileStream == null) { logger.info("failed to find " + fileName + " with class " + "loader, will continue without it."); return; } try { fileProps.load(fileStream); } finally { fileStream.close(); } // now get those properties and place them into the mutable and // immutable properties maps. for (Map.Entry entry : fileProps.entrySet()) { String name = (String) entry.getKey(); String value = (String) entry.getValue(); if ( name == null || value == null || name.trim().length() == 0) { continue; } if (name.startsWith("*")) { name = name.substring(1); if(name.trim().length() == 0) { continue; } //it seems that we have a valid default immutable property immutableDefaultProperties.put(name, value); //in case this is an override, make sure we remove previous //definitions of this property defaultProperties.remove(name); } else { //this property is a regular, mutable default property. defaultProperties.put(name, value); //in case this is an override, make sure we remove previous //definitions of this property immutableDefaultProperties.remove(name); } } } catch (Exception ex) { //we can function without defaults so we are just logging those. logger.info("No defaults property file loaded: " + fileName + ". Not a problem."); if(logger.isDebugEnabled()) logger.debug("load exception", ex); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy