/*
* Copyright (c) 2013-2014 the original author or authors
*
* 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 io.werval.runtime;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import io.werval.api.Config;
import io.werval.util.Reflectively;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static io.werval.util.Charsets.UTF_8;
/**
* Config Instance backed by TypeSafe Config.
*/
@Reflectively.Loaded( by = "DevShell" )
public class ConfigInstance
implements Config
{
/**
* Helper to preserve config location and overrides between reloads.
* <p>
* See {@link ConfigInstance#location()}.
*/
public static final class ConfigLocation
{
private final String configResource;
private final File configFile;
private final URL configUrl;
private final Map<String, Object> overrides;
private ConfigLocation()
{
this( null, null, null, null );
}
private ConfigLocation( String configResource )
{
this( configResource, null, null, null );
}
private ConfigLocation( File configFile )
{
this( null, configFile, null, null );
}
private ConfigLocation( URL configUrl )
{
this( null, null, configUrl, null );
}
private ConfigLocation( String configResource, File configFile, URL configUrl, Map<String, Object> overrides )
{
this.configResource = configResource;
this.configFile = configFile;
this.configUrl = configUrl;
this.overrides = overrides;
}
public String toStringShort()
{
StringBuilder sb = new StringBuilder();
boolean previous = false;
if( configResource != null )
{
sb.append( "resource:" ).append( configResource );
previous = true;
}
if( configFile != null )
{
if( previous )
{
sb.append( ", " );
}
sb.append( "file:" ).append( configFile.getAbsolutePath() );
previous = true;
}
if( configUrl != null )
{
if( previous )
{
sb.append( ", " );
}
sb.append( "url:" ).append( configUrl.toString() );
previous = true;
}
if( overrides != null )
{
if( previous )
{
sb.append( ", " );
}
sb.append( "overrides:" ).append( overrides.keySet().toString() );
}
return sb.toString();
}
@Override
public String toString()
{
return "ConfigLocation{" + toStringShort() + '}';
}
}
private final ConfigLocation location;
private com.typesafe.config.Config config;
/**
* Create a new Config instance.
*
* @param loader ClassLoader
*/
public ConfigInstance( ClassLoader loader )
{
this( loader, new ConfigLocation() );
}
/**
* Create a new Config instance.
*
* @param loader ClassLoader
* @param configResource Configuration resource name
*/
public ConfigInstance( ClassLoader loader, String configResource )
{
this( loader, new ConfigLocation( configResource ) );
}
/**
* Create a new Config instance.
*
* @param loader ClassLoader
* @param configFile Configuration file
*/
public ConfigInstance( ClassLoader loader, File configFile )
{
this( loader, new ConfigLocation( configFile ) );
}
/**
* Create a new Config instance.
*
* @param loader ClassLoader
* @param configUrl Configuration URL
*/
public ConfigInstance( ClassLoader loader, URL configUrl )
{
this( loader, new ConfigLocation( configUrl ) );
}
/**
* Create a new Config instance.
* <p>
* Only one of {@literal configResource}, {@literal configFile} or {@literal configUrl} should be non-null.
*
* @param loader ClassLoader
* @param configResource Configuration resource name
* @param configFile Configuration file
* @param configUrl Configuration URL
* @param overrides Configuration properties overrides, may be null
*/
@Reflectively.Invoked( by = "DevShell" )
public ConfigInstance(
ClassLoader loader,
String configResource,
File configFile,
URL configUrl,
Map<String, Object> overrides
)
{
this( loader, new ConfigLocation( configResource, configFile, configUrl, overrides ) );
}
public ConfigInstance( ClassLoader loader, ConfigLocation location )
{
// Gather eventually set previous system properties for config location
String previousConfigResource = System.getProperty( "config.resource" );
String previousConfigFile = System.getProperty( "config.file" );
String previousConfigURL = System.getProperty( "config.url" );
Map<String, String> overridesPrevious = new HashMap<>();
try
{
// Eventually set config.resource
if( location.configResource != null )
{
System.setProperty( "config.resource", location.configResource );
}
// Eventually set config.file
if( location.configFile != null )
{
System.setProperty( "config.file", location.configFile.getAbsolutePath() );
}
// Eventually set config.url
if( location.configUrl != null )
{
System.setProperty( "config.url", location.configUrl.toExternalForm() );
}
// Eventually set configuration properties overrides
if( location.overrides != null )
{
location.overrides.forEach(
(key, val) ->
{
overridesPrevious.put( key, System.getProperty( key ) );
System.setProperty( key, Objects.toString( val ) );
}
);
}
// Hold a reference to the location, for reload purpose
this.location = location;
// Invalidate TypeSafe Config caches (system properties)
com.typesafe.config.ConfigFactory.invalidateCaches();
// Effectively load configuration
this.config = com.typesafe.config.ConfigFactory.load( loader );
}
finally
{
// Restore config.resource
if( previousConfigResource == null )
{
System.clearProperty( "config.resource" );
}
else
{
System.setProperty( "config.resource", previousConfigResource );
}
// Restore config.file
if( previousConfigFile == null )
{
System.clearProperty( "config.file" );
}
else
{
System.setProperty( "config.file", previousConfigFile );
}
// Restore config.url
if( previousConfigURL == null )
{
System.clearProperty( "config.url" );
}
else
{
System.setProperty( "config.url", previousConfigURL );
}
// Restore overrides
overridesPrevious.forEach(
(key, val) ->
{
if( val == null )
{
System.clearProperty( key );
}
else
{
System.setProperty( key, val );
}
}
);
}
}
/**
* Internal CTOR.
* <p>
* See {@link #location()}, {@link #atPath(java.lang.String)} and {@link #array(java.lang.String)}.
*
* @param config TypeSafe Config
* @param location Config location and overrides
*/
private ConfigInstance( com.typesafe.config.Config config, ConfigLocation location )
{
this.location = location;
this.config = config;
}
/**
* Config Location Helper.
* <p>
* Used internally by {@link ApplicationInstance} to preserve config location and overrides between reloads.
* See {@link #ConfigInstance(com.typesafe.config.Config, io.werval.runtime.ConfigInstance.ConfigLocation)}.
*
* @return Config Location Helper
*/
/* package */ ConfigLocation location()
{
return location;
}
@Override
public boolean isObject( String key )
{
try
{
config.getConfig( key );
return true;
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing noObject )
{
return false;
}
}
@Override
public boolean has( String key )
{
return config.hasPath( key );
}
@Override
public boolean isArray( String key )
{
try
{
config.getConfigList( key );
return true;
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing noArray )
{
return false;
}
}
@Override
public Config atKey( String key )
{
return new ConfigInstance( config.getConfig( '"' + key + '"' ), location );
}
@Override
public Optional<Config> atKeyOptional( String key )
{
try
{
return Optional.of( atKey( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Config atPath( String key )
{
return new ConfigInstance( config.getConfig( key ), location );
}
@Override
public Optional<Config> atPathOptional( String path )
{
try
{
return Optional.of( atPath( path ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public List<Config> array( String key )
{
List<Config> configs = new ArrayList<>();
config.getConfigList( key ).forEach( conf -> configs.add( new ConfigInstance( conf, location ) ) );
return configs;
}
@Override
public Optional<List<Config>> arrayOptional( String key )
{
try
{
return Optional.of( array( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Set<String> subKeys()
{
return config.root().keySet();
}
@Override
public Boolean bool( String key )
{
return config.getBoolean( key );
}
@Override
public Optional<Boolean> boolOptional( String key )
{
try
{
return Optional.of( bool( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Integer intNumber( String key )
{
return config.getInt( key );
}
@Override
public Optional<Integer> intOptional( String key )
{
try
{
return Optional.of( intNumber( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Long longNumber( String key )
{
return config.getLong( key );
}
@Override
public Optional<Long> longOptional( String key )
{
try
{
return Optional.of( longNumber( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Double doubleNumber( String key )
{
return config.getDouble( key );
}
@Override
public Optional<Double> doubleOptional( String key )
{
try
{
return Optional.of( doubleNumber( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public String string( String key )
{
return config.getString( key );
}
@Override
public Optional<String> stringOptional( String key )
{
try
{
return Optional.of( string( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public boolean isList( String key )
{
try
{
config.getList( key );
return true;
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing noList )
{
return false;
}
}
@Override
public List<Boolean> boolList( String key )
{
return config.getBooleanList( key );
}
@Override
public Optional<List<Boolean>> boolListOptional( String key )
{
try
{
return Optional.of( boolList( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public List<Integer> intList( String key )
{
return config.getIntList( key );
}
@Override
public Optional<List<Integer>> intListOptional( String key )
{
try
{
return Optional.of( intList( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public List<Double> doubleList( String key )
{
return config.getDoubleList( key );
}
@Override
public Optional<List<Double>> doubleListOptional( String key )
{
try
{
return Optional.of( doubleList( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public List<String> stringList( String key )
{
return config.getStringList( key );
}
@Override
public Optional<List<String>> stringListOptional( String key )
{
try
{
return Optional.of( stringList( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Map<String, String> stringMap( String key )
{
Set<Entry<String, com.typesafe.config.ConfigValue>> entrySet = config.getObject( key ).entrySet();
Map<String, String> entries = new HashMap<>( entrySet.size() );
for( Entry<String, com.typesafe.config.ConfigValue> entry : entrySet )
{
entries.put( entry.getKey(), entry.getValue().unwrapped().toString() );
}
return entries;
}
@Override
public Optional<Map<String, String>> stringMapOptional( String key )
{
try
{
return Optional.of( stringMap( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public char[] chars( String key )
{
return config.getString( key ).toCharArray();
}
@Override
public Optional<char[]> charsOptional( String key )
{
try
{
return Optional.of( chars( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public byte[] utf8Bytes( String key )
{
return config.getString( key ).getBytes( UTF_8 );
}
@Override
public Optional<byte[]> utf8BytesOptional( String key )
{
try
{
return Optional.of( utf8Bytes( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Charset charset( String key )
{
return Charset.forName( config.getString( key ) );
}
@Override
public Optional<Charset> charsetOptional( String key )
{
try
{
return Optional.of( charset( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public URL url( String key )
{
String urlString = config.getString( key );
try
{
return new URL( urlString );
}
catch( MalformedURLException ex )
{
throw new com.typesafe.config.ConfigException.WrongType(
config.origin(),
"Malformed URL in config, key: " + key + ", value: " + urlString,
ex
);
}
}
@Override
public Optional<URL> urlOptional( String key )
{
try
{
return Optional.of( url( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public File file( String key )
{
return new File( config.getString( key ) );
}
@Override
public Optional<File> fileOptional( String key )
{
try
{
return Optional.of( file( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Long seconds( String key )
{
return config.getDuration( key, SECONDS );
}
@Override
public Optional<Long> secondsOptional( String key )
{
try
{
return Optional.of( seconds( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public Long milliseconds( String key )
{
return config.getDuration( key, MILLISECONDS );
}
@Override
public Optional<Long> millisecondsOptional( String key )
{
try
{
return Optional.of( milliseconds( key ) );
}
catch( com.typesafe.config.ConfigException.WrongType | com.typesafe.config.ConfigException.Missing ex )
{
return Optional.empty();
}
}
@Override
public String toString()
{
return config.root().render( com.typesafe.config.ConfigRenderOptions.concise() );
}
public final void refresh()
{
config = com.typesafe.config.ConfigFactory.load();
}
}