org.neo4j.kernel.configuration.Config Maven / Gradle / Ivy
Show all versions of neo4j-kernel Show documentation
/*
* Copyright (c) 2002-2015 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.kernel.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.neo4j.graphdb.config.Setting;
import org.neo4j.helpers.Function;
import org.neo4j.helpers.Functions;
import org.neo4j.helpers.collection.Iterables;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.kernel.info.DiagnosticsPhase;
import org.neo4j.kernel.info.DiagnosticsProvider;
import org.neo4j.kernel.logging.BufferingLogger;
import static java.lang.Character.isDigit;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
/**
* This class holds the overall configuration of a Neo4j database instance. Use the accessors
* to convert the internal key-value settings to other types.
*
* Users can assume that old settings have been migrated to their new counterparts, and that defaults
* have been applied.
*
* UI's can change configuration by calling applyChanges. Any listener, such as services that use
* this configuration, can be notified of changes by implementing the {@link ConfigurationChangeListener} interface.
*/
public class Config implements DiagnosticsProvider
{
private final List listeners = new CopyOnWriteArrayList<>();
private final Map params = new ConcurrentHashMap<>( );
private final Function settingsFunction;
// Messages to this log get replayed into a real logger once logging has been
// instantiated.
private StringLogger log = new BufferingLogger();
private Iterable> settingsClasses = emptyList();
private ConfigurationMigrator migrator;
private ConfigurationValidator validator;
public Config()
{
this( new HashMap(), Collections.>emptyList() );
}
public Config( Map inputParams )
{
this( inputParams, Collections.>emptyList() );
}
public Config( Map inputParams, Class>... settingsClasses )
{
this( inputParams, asList( settingsClasses ) );
}
public Config( Map inputParams, Iterable> settingsClasses )
{
this.settingsFunction = Functions.map( params );
this.params.putAll( inputParams );
registerSettingsClasses( settingsClasses );
}
/** Add more settings classes */
public Config registerSettingsClasses( Class> ... settingsClasses )
{
return registerSettingsClasses( asList(settingsClasses) );
}
/** Add more settings classes. */
public Config registerSettingsClasses( Iterable> settingsClasses )
{
this.settingsClasses = Iterables.concat( settingsClasses, this.settingsClasses );
this.migrator = new AnnotationBasedConfigurationMigrator( settingsClasses );
this.validator = new ConfigurationValidator( settingsClasses );
// Apply the requirements and changes the new settings classes introduce
this.applyChanges( getParams() );
return this;
}
// TODO: Get rid of this, to allow us to have something more
// elaborate as internal storage (eg. something that can keep meta data with
// properties).
public Map getParams()
{
return new HashMap<>( this.params );
}
/**
* Retrieve a configuration property.
*/
public T get( Setting setting )
{
return setting.apply( settingsFunction );
}
/**
* Use {@link Config#applyChanges(java.util.Map)} instead, so changes are applied in
* bulk and the ConfigurationChangeListeners can process the changes in one go.
*/
@Deprecated
public Config setProperty( String key, Object value )
{
// This method here is for supporting legacy server configurator api.
// None should call this except external users,
// as "ideally" properties should not be changed once they are loaded.
this.params.put( key, value.toString() );
this.applyChanges( new HashMap<>( params ) );
return this;
}
/**
* Replace the current set of configuration parameters with another one.
*/
public synchronized void applyChanges( Map newConfiguration )
{
newConfiguration = migrator.apply( newConfiguration, log );
// Make sure all changes are valid
validator.validate( newConfiguration );
// Figure out what changed
if ( listeners.isEmpty() )
{
// Make the change
params.clear();
params.putAll( newConfiguration );
}
else
{
List configurationChanges = new ArrayList<>();
for ( Map.Entry stringStringEntry : newConfiguration.entrySet() )
{
String oldValue = params.get( stringStringEntry.getKey() );
String newValue = stringStringEntry.getValue();
if ( !(oldValue == null && newValue == null) &&
(oldValue == null || newValue == null || !oldValue.equals( newValue )) )
{
configurationChanges.add( new ConfigurationChange( stringStringEntry.getKey(), oldValue,
newValue ) );
}
}
if ( configurationChanges.isEmpty() )
{
// Don't bother... nothing changed.
return;
}
// Make the change
params.clear();
for ( Map.Entry entry : newConfiguration.entrySet() )
{
// Filter out nulls because we are using a ConcurrentHashMap under the covers, which doesn't support
// null keys or values.
String value = entry.getValue();
if ( value != null )
{
params.put( entry.getKey(), value );
}
}
// Notify listeners
for ( ConfigurationChangeListener listener : listeners )
{
listener.notifyConfigurationChanges( configurationChanges );
}
}
}
public Iterable> getSettingsClasses()
{
return settingsClasses;
}
public void setLogger( StringLogger log )
{
if ( this.log instanceof BufferingLogger )
{
((BufferingLogger) this.log).replayInto( log );
}
this.log = log;
}
public void addConfigurationChangeListener( ConfigurationChangeListener listener )
{
listeners.add( listener );
}
public void removeConfigurationChangeListener( ConfigurationChangeListener listener )
{
listeners.remove( listener );
}
@Override
public String getDiagnosticsIdentifier()
{
return getClass().getName();
}
@Override
public void acceptDiagnosticsVisitor( Object visitor )
{
// nothing visits configuration
}
@Override
public void dump( DiagnosticsPhase phase, StringLogger log )
{
if ( phase.isInitialization() || phase.isExplicitlyRequested() )
{
log.logLongMessage( "Neo4j Kernel properties:", Iterables.map( new Function,
String>()
{
@Override
public String apply( Map.Entry stringStringEntry )
{
return stringStringEntry.getKey() + "=" + stringStringEntry.getValue();
}
}, params.entrySet() ) );
}
}
@Override
public String toString()
{
List keys = new ArrayList<>( params.keySet() );
Collections.sort( keys );
LinkedHashMap output = new LinkedHashMap<>();
for ( String key : keys )
{
output.put( key, params.get( key ) );
}
return output.toString();
}
public static long parseLongWithUnit( String numberWithPotentialUnit )
{
int firstNonDigitIndex = findFirstNonDigit( numberWithPotentialUnit );
String number = numberWithPotentialUnit.substring( 0, firstNonDigitIndex );
long multiplier = 1;
if ( firstNonDigitIndex < numberWithPotentialUnit.length() )
{
String unit = numberWithPotentialUnit.substring( firstNonDigitIndex );
if ( unit.equalsIgnoreCase( "k" ) )
{
multiplier = 1024;
}
else if ( unit.equalsIgnoreCase( "m" ) )
{
multiplier = 1024 * 1024;
}
else if ( unit.equalsIgnoreCase( "g" ) )
{
multiplier = 1024 * 1024 * 1024;
}
else
{
throw new IllegalArgumentException(
"Illegal unit '" + unit + "' for number '" + numberWithPotentialUnit + "'" );
}
}
return Long.parseLong( number ) * multiplier;
}
/**
* @return index of first non-digit character in {@code numberWithPotentialUnit}. If all digits then
* {@code numberWithPotentialUnit.length()} is returned.
*/
private static int findFirstNonDigit( String numberWithPotentialUnit )
{
int firstNonDigitIndex = numberWithPotentialUnit.length();
for ( int i = 0; i < numberWithPotentialUnit.length(); i++ )
{
if ( !isDigit( numberWithPotentialUnit.charAt( i ) ) )
{
firstNonDigitIndex = i;
break;
}
}
return firstNonDigitIndex;
}
/**
* Returns a copy of this config with the given modifications.
* @return a new modified config, leaves this config unchanged.
*/
public Config with( Map additionalConfig )
{
Map newParams = getParams(); // copy is returned
newParams.putAll( additionalConfig );
return new Config( newParams );
}
}