
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 extends ConfigurationStore>
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 extends ConfigurationStore>
getDefaultConfigurationStoreClass()
{
Class extends ConfigurationStore> 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 extends ConfigurationStore>) 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